Click here to Skip to main content
13,829,136 members
Click here to Skip to main content

Stats

730.1K views
29.9K downloads
281 bookmarked
Posted 14 Sep 2010
Licenced CPOL

PVS.AVPlayer - MCI Audio and Video Library

, 7 Aug 2018
Windows Media Control Interface (MCI) library with many added features
PVS.AVPlayer
PVS.AVPlayer .NET 2.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 3.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 3.5
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.0
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.5
PVS.AVPlayer .NET 4.5.1
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.5.2
PVS.AVPlayer.XML
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6.1
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.6.2
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.7
PVS.AVPlayer .NET 4.7.1
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer .NET 4.7.2
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer.dll
PVS.AVPlayer.XML
PVS.AVPlayer All Source Code
AVPlayerExample
AVPlayerExample
AVPlayerExample.csproj.user
bin
Debug
PVS.AVPlayer.XML
Release
Dialogs
Display Overlays
obj
Debug
Release
x86
Debug
Release
Properties
Resources
Crystal Italic1.ttf
WingDings3a.ttf
Voice Recorder
FolderView
FolderView
bin
Debug
PVS.AVPlayer.XML
Release
FolderView.csproj.user
obj
Release
x86
Debug
Release
Properties
Resources
Crystal Italic1.ttf
PVS.AVPlayer
AVPlayerExample.csproj.user
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Custom Items
Native Methods
Bob.png
Crystal Italic1.ttf
Dial Green 2.png
Dial Green 4.png
Dial Green.png
Dial Red 2.png
Dial Red.png
media7.ico
media7a.ico
Media8.ico
Media8a.ico
VU Meter.png
WingDings3a.ttf
Sound Recorder
Various
About Dialog
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Custom Items
FolderView.csproj.user
Debug
Bob.png
Crystal Italic1.ttf
media7a.ico
media7b.ico
Media8a.ico
Media8b.ico
Subtitles Overlay
Various
How To (C#)
PVSAVPlayerHowTo
bin
Debug
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Release
obj
Debug
Release
Properties
How To (VB.NET)
PVSAVPlayerHowToVB
bin
Debug
PVS.AVPlayer.dll
PVS.AVPlayer.XML
Release
My Project
Application.myapp
obj
Debug
Release
PVSAVPlayerHowTo.vbproj.user
PVS.AVPlayer Examples
AVPlayerExample.ex_
FolderView.ex_
AVPlayerExample.exe
FolderView.exe
PVS.AVPlayer.dll
/****************************************************************
    
    PVS.AVPlayer - Version 0.46
    September 2015, The Netherlands
    © Copyright 2015 PVS The Netherlands

    ****************

    The PVS.AVPlayer library source code is divided into 3 files:
    1. Player.cs - (this file) Player source code
    2. Recorder.cs - (Microphone) Recorder source code
    3. PlayerRecorder.cs - code used by both Player.cs and Recorder.cs
    
    Required references:
    System
    System.Drawing
    System.Windows.Forms

    ****************
 
    This file: Player.cs

    Player Class
    Uses file 'PlayerRecorder.cs'

    ****************

    About Media Control Interface (MCI)
    - for information about MCI, please see https://msdn.microsoft.com/en-us/library/windows/desktop/dd757151%28v=vs.85%29.aspx
    - you can find very many articles about mci on the internet, search examples: 'c# mci', 'mcicommandstring'
      or the subject of your question.

    The PVS.AVPlayer.Player 'operating steps' for every mediafile to play:
    1. open player (mci device; e.g. 'open "C:\MyMovies\MyMovie.mpg" type mpegvideo alias myPlayer')
    2. set player properties (e.g. 'set myPlayer time format ms' / 'setaudio myPlayer left volume to 500')
    3. start playing (e.g. 'play myPlayer')
    4. stop playing (e.g. 'stop myPlayer' or when media has finished playing (mci notify / Media Ended event))
    5. close player (mci device; e.g. 'close myPlayer')
 
    The 'open' and 'close' device commands are 'hidden' from the user:
    The user only has to use the 'play' and 'stop' commands.

    ****************

    Thanks!

    Many thanks to Microsoft (Windows, .NET Framework, Visual Studio Express, etc.), all the people
    writing about programming on the internet (a great source for ideas and solving problems),
    the websites publishing those or other writings about programming, the people responding
    to the PVS.AVPlayer article with comments and suggestions and, of course, The Code Project:
    thank you Deeksha, Smitha and Sean for the beautiful article and all!

    ****************************************************************/


#region Usings

using System;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

#endregion

namespace PVS.AVPlayer
{
    // ******************************** PVS.AVPlayer - Enumerations (Player)

    #region PVS.AVPlayer - Enumerations (Player) - DisplayMode/FullScreenMode/OverlayMode/MediaLength/MediaName/ScreenCopyMode/PositionSliderMode

    /// <summary>
    /// Specifies the size and location of the video image on the player's display.
    /// </summary>
    public enum DisplayMode
    {
        /// <summary>
        /// Size: original size.
        /// Location: topleft of the player's display.
        /// Display resize: shrink: no, grow: no.
        /// </summary>
        Normal,
        /// <summary>
        /// Size: original size.
        /// Location: center of the player's display.
        /// Display resize: shrink: no, grow: no.
        /// </summary>
        Center,
        /// <summary>
        /// Size: same size as the player's display.
        /// Location: topleft of the player's display.
        /// Display resize: shrink: yes, grow: yes.
        /// </summary>
        Stretch,
        /// <summary>
        /// Size: same size as the player's display preserving size ratio.
        /// Location: topleft of the player's display.
        /// Display resize: shrink: yes, grow: yes.
        /// </summary>
        Zoom,
        /// <summary>
        /// Size: same as the player's display preserving size ratio.
        /// Location: center of the player's display.
        /// Display resize shrink: yes, grow: yes.
        /// </summary>
        ZoomAndCenter,
        /// <summary>
        /// Size: original size or same as the player's display preserving size ratio.
        /// Location: topleft of the player's display.
        /// Display resize: shrink: yes, grow: if smaller than original size.
        /// </summary>
        SizeToFit,
        /// <summary>
        /// Size: original size or same as the player's display preserving size ratio.
        /// Location: center of the player's display.
        /// Display resize: shrink: yes, grow: if smaller than original size.
        /// </summary>
        SizeToFitCenter,
        /// <summary>
        /// Size: set manually.
        /// Location: set manually.
        /// Display resize: shrink: no, grow: no.
        /// </summary>
        Manual
    }

    /// <summary>
    /// Specifies the player's fullscreen mode.
    /// </summary>
    public enum FullScreenMode
    {
        /// <summary>
        /// The player's display is shown fullscreen.
        /// </summary>
        Display,
        /// <summary>
        /// The (parent) control that contains the player's display is shown fullscreen.
        /// </summary>
        Parent,
        /// <summary>
        /// The form that contains the player's display is shown fullscreen.
        /// </summary>
        Form
    }

    /// <summary>
    /// Specifies the size mode of the player's display overlay.
    /// </summary>
    public enum OverlayMode
    {
        /// <summary>
        /// The overlay has the same size and position as the player's display.
        /// </summary>
        Display,
        /// <summary>
        /// The overlay has the same size and position as the (visible part of the) video image on the player's display.
        /// </summary>
        Video
    }

    /// <summary>
    /// Specifies the part of the playing media to get the length (duration) of.
    /// </summary>
    public enum MediaLength
    {
        /// <summary>
        /// The total length (duration) of the playing mediafile.
        /// </summary>
        Total,
        /// <summary>
        /// The length (duration) of the playing mediafile from the start of the file to the current position.
        /// </summary>
        FromStart,
        /// <summary>
        /// The length (duration) of the playing mediafile from 'StartPosition' to the current position.
        /// </summary>
        FromStartPosition,
        /// <summary>
        /// The length (duration) of the playing mediafile from the current position to the end of file.
        /// </summary>
        ToEnd,
        /// <summary>
        /// The length (duration) of the playing mediafile from the current position to 'EndPosition'.
        /// </summary>
        ToEndPosition,
        /// <summary>
        /// The length (duration) of the playing mediafile from 'StartPosition' to 'EndPosition'.
        /// </summary>
        StartToEndPosition,
    }

    /// <summary>
    /// Specifies the part of the filename of the playing media. 
    /// </summary>
    public enum MediaName
    {
        /// <summary>
        /// The playing media's filename without path and extension.
        /// </summary>
        FileNameWithoutExtension,
        /// <summary>
        /// The playing media's filename and extension without path.
        /// </summary>
        FileName,
        /// <summary>
        /// The playing media's filename with path and extension.
        /// </summary>
        FullPath,
        /// <summary>
        /// The playing media's filename extension.
        /// </summary>
        Extension,
        /// <summary>
        /// The playing media's filename path (directory).
        /// </summary>
        DirectoryName,
        /// <summary>
        /// The playing media's filename root path (root directory).
        /// </summary>
        PathRoot
    }

    /// <summary>
    /// Specifies the area of the screen that will be copied.
    /// </summary>
    public enum ScreenCopyMode
    {
        /// <summary>
        /// The (visible part of the) video image on the player's display.
        /// </summary>
        Video,
        /// <summary>
        /// The player's display.
        /// </summary>
        Display,
        /// <summary>
        /// The (parent) control that contains the player's display.
        /// </summary>
        Parent,
        /// <summary>
        /// The display area of the form that contains the player's display.
        /// </summary>
        Form,
        /// <summary>
        /// The (entire) screen that contains the player's display.
        /// </summary>
        Screen
    }

    /// <summary>
    /// Specifies the display mode of the positionslider controlled by the player.
    /// </summary>
    public enum PositionSliderMode
    {
        /// <summary>
        /// The positionslider shows the playback position from the media's StartPosition to the media's EndPosition.
        /// </summary>
        Progress,
        /// <summary>
        /// The positionslider shows the playback position from the natural beginning of the media to the natural end of the media.
        /// </summary>
        Track
    }

    #endregion

    /// <summary>
    /// Represents a mediaplayer that can be used to playback mediafiles using Microsoft Windows built-in Media Control Interface (MCI).
    /// </summary>
    [CLSCompliant(true)]
    public class Player : IDisposable
    {
        // ******************************** Event Declarations

        #region Event Declarations

        /// <summary>
        /// Occurs when media has finished playing.
        /// </summary>
        public event EventHandler MediaEnded;

        /// <summary>
        /// Occurs when media has finished playing, just before the MediaEnded event occurs.
        /// </summary>
        public event EventHandler MediaEndedNotice;

        /// <summary>
        /// Occurs when playback of next media is requested.
        /// </summary>
        public event EventHandler MediaNextRequested;

        /// <summary>
        /// Occurs when playback of previous media is requested.
        /// </summary>
        public event EventHandler MediaPreviousRequested;

        /// <summary>
        /// Occurs when the player's repeat setting has changed.
        /// </summary>
        public event EventHandler MediaRepeatChanged;

        /// <summary>
        /// Occurs when media has finished playing and is about to be repeated.
        /// </summary>
        public event EventHandler MediaRepeating;

        /// <summary>
        /// Occurs when media has finished playing and is repeated.
        /// </summary>
        public event EventHandler MediaRepeated;

        /// <summary>
        /// Occurs when a mediafile is opened to be played.
        /// </summary>
        public event EventHandler MediaOpened;

        /// <summary>
        /// Occurs when media starts playing.
        /// </summary>
        public event EventHandler MediaStarted;

        /// <summary>
        /// Occurs when the player's pause mode is activated (playing media is paused).
        /// </summary>
        public event EventHandler MediaPaused;

        /// <summary>
        /// Occurs when the player's pause mode is deactivated (paused media resumes playing).
        /// </summary>
        public event EventHandler MediaResumed;

        /// <summary>
        /// Occurs when media has stopped playing by using the player's Stop function.
        /// </summary>
        public event EventHandler MediaStopped;

        /// <summary>
        /// Occurs when media has stopped playing, just before the MediaStopped event occurs.
        /// </summary>
        public event EventHandler MediaStoppedNotice;

        /// <summary>
        /// Occurs when the playback position of the playing media has changed.
        /// </summary>
        public event EventHandler MediaPositionChanged;

        /// <summary>
        /// Occurs when the player's start- and/or endposition (for the next media to play) has changed.
        /// </summary>
        public event EventHandler MediaStartEndChanged;

        /// <summary>
        /// Occurs when the playing media's start- and/or endposition has changed.
        /// </summary>
        public event EventHandler MediaStartEndMediaChanged;

        /// <summary>
        /// Occurs when the player's display has changed.
        /// </summary>
        public event EventHandler MediaDisplayChanged;

        /// <summary>
        /// Occurs when the player's displaymode has changed.
        /// </summary>
        public event EventHandler MediaDisplayModeChanged;

        /// <summary>
        /// Occurs when the player's fullscreen setting has changed.
        /// </summary>
        public event EventHandler MediaFullScreenChanged;

        /// <summary>
        /// Occurs when the player's fullscreen mode has changed.
        /// </summary>
        public event EventHandler MediaFullScreenModeChanged;

        /// <summary>
        /// Occurs when the player's audio volume has changed.
        /// </summary>
        public event EventHandler MediaAudioVolumeChanged;

        /// <summary>
        /// Occurs when the player's audio balance has changed.
        /// </summary>
        public event EventHandler MediaAudioBalanceChanged;

        /// <summary>
        /// Occurs when the player's audio enabled setting has changed.
        /// </summary>
        public event EventHandler MediaAudioEnabledChanged;

        /// <summary>
        /// Occurs when the player's video enabled setting has changed.
        /// </summary>
        public event EventHandler MediaVideoEnabledChanged;

        /// <summary>
        /// Occurs when the video's videobounds have changed (by using VideoBounds, VideoZoom or VideoMove options).
        /// </summary>
        public event EventHandler MediaVideoBoundsChanged;

        /// <summary>
        /// Occurs when the player's playback speed setting has changed.
        /// </summary>
        public event EventHandler MediaSpeedChanged;

        /// <summary>
        /// Occurs when the player's display overlay has changed.
        /// </summary>
        public event EventHandler MediaOverlayChanged;

        /// <summary>
        /// Occurs when the player's display overlay mode setting has changed.
        /// </summary>
        public event EventHandler MediaOverlayModeChanged;

        /// <summary>
        /// Occurs when the player's display overlay hold setting has changed.
        /// </summary>
        public event EventHandler MediaOverlayHoldChanged;

        #endregion

        // ******************************** Fields

        #region Fields

        #region Constants

        // MCI Default Values
        private const bool              DEFAULT_VIDEO_ENABLED       = true;
        private const bool              DEFAULT_AUDIO_ENABLED       = true;
        private const int               DEFAULT_AUDIO_VOLUME        = 1000;
        private const int               DEFAULT_AUDIO_BALANCE       = 500;
        private const int               DEFAULT_SPEED               = 1000;

        // Player Default Values
        private const DisplayMode       DEFAULT_DISPLAYMODE         = DisplayMode.ZoomAndCenter;
        private const FullScreenMode    DEFAULT_FULLSCREENMODE      = FullScreenMode.Display;
        private const OverlayMode       DEFAULT_OVERLAYMODE         = OverlayMode.Video;

        // Timers default values
        private const int               DEFAULT_TIMERINTERVAL       = 200;
        private const int               DEFAULT_MINIMIZEDINTERVAL   = 300;

        // Used with MciNotifyClass
        private const int               MM_MCINOTIFY                = 0x03B9;
        private const int               MCI_NOTIFY_SUCCESS          = 0x01;
        //private const int             MCI_NOTIFY_SUPERSEDED       = 0x02;
        //private const int             MCI_NOTIFY_ABORTED          = 0x04;
        //private const int             MCI_NOTIFY_FAILURE          = 0x08;

        #endregion

        // This class is used only to receive the MCI notification 'End Of Media'
        #region MCI Notify Class

        private class MciNotifyClass : Control
        {
            internal Player Player;

            protected override void WndProc(ref Message m)
            {
                if (m.Msg == MM_MCINOTIFY)
                {
                    if (m.WParam.ToInt32() == MCI_NOTIFY_SUCCESS)
                    {
                        Player.AV_EndOfMedia(true);
                    }
                }
                else base.WndProc(ref m);
            }
        }
        private MciNotifyClass  _mciNotify;

        #endregion

        // Used with all players fullscreen management
        private static Form     _fullScreenForm1;
        private static Form     _fullScreenForm2;
        private static Form     _fullScreenForm3;
        private static Form     _fullScreenForm4;

        // MCI communication buffers
        private StringBuilder   _mciBuffer;
        private StringBuilder   _mciSmallBuffer1;
        private StringBuilder   _mciSmallBuffer2;
        private StringBuilder   _paintCommand;

        // MCI device
        private string          _deviceId;
        private bool            _hasDevice;

        // Last error
        private int             _lastError;

        // Display
        private Control         _display;
        private bool            _hasDisplay;
        private bool            _hasDisplayEvents;
        private bool            _resizeFormRefresh;
        private DisplayMode     _displayMode = DEFAULT_DISPLAYMODE;
        private bool            _hasOwnWindow;

        // Display Overlay
        private Form            _overlay;
        private OverlayMode     _overlayMode = DEFAULT_OVERLAYMODE;
        private bool            _overlayHold;
        private bool            _overlayCanFocus;
        private bool            _hasOverlay;
        private bool            _hasOverlayMenu;
        private bool            _hasOverlayShown;
        private bool            _hasOverlayEvents;
        private bool            _hasOverlayFocusEvents;
        private Rectangle       _intersect;

        // Full Screen
        private bool            _fullScreen;
        private FullScreenMode  _fullScreenMode = DEFAULT_FULLSCREENMODE;
        private Rectangle       _fsFormBounds;
        private FormBorderStyle _fsFormBorder;
        private Rectangle       _fsParentBounds;
        private BorderStyle     _fsParentBorder;
        private int             _fsParentIndex;
        private Rectangle       _fsDisplayBounds;
        private BorderStyle     _fsDisplayBorder;
        private int             _fsDisplayIndex;

        // Player / Media
        private string          _fileName;
        private int             _startPosition;
        private int             _startPositionMedia;
        private int             _endPosition;
        private int             _endPositionMedia;
        private bool            _seeking;
        private bool            _seekSkipped;
        private int             _seekSkippedPosition;
        private bool            _repeat;
        private bool            _playing;
        private bool            _paused;
        private bool            _resumeIsPlay;
        private int             _speed = DEFAULT_SPEED;
        private int             _mciSpeed;
        private bool            _speedSkipped;
        private bool            _hasMediaLength;
        private int             _mediaLength;
        private bool            _busyStarting;

        // PlayerStartInfo
        private string          _siFileName;
        private Control         _siDisplay;
        private TimeSpan        _siStartPosition;
        private TimeSpan        _siEndPosition;
        private bool            _siRepeat;

        // Video
        private bool            _hasVideo;
        private bool            _videoEnabled = DEFAULT_VIDEO_ENABLED;
        private bool            _mciVideoEnabled = true;
        private bool            _hasVideoBounds;
        private Rectangle       _videoBounds;
        private bool            _hasVideoSourceSize;
        private Size            _videoSourceSize;

        // Audio
        private bool            _hasAudio;
        private bool            _mciAudioEnabled = true;
        private bool            _audioEnabled = DEFAULT_AUDIO_ENABLED;
        private int             _mciAudioVolume;
        private int             _audioVolume = DEFAULT_AUDIO_VOLUME;
        private int             _audioBalance = DEFAULT_AUDIO_BALANCE;

        // ScreenCopy
        private ScreenCopyMode  _screenCopyMode = ScreenCopyMode.Video;

        // Timer used with position changed events
        private Timer           _timer;
        private bool            _timerEnabled;

        // Minimized
        private bool            _minimizeEnabled = true;
        private bool            _minimizeHasEvent;
        private bool            _minimized;
        private Timer           _minimizeTimer;
        private int             _minimizedInterval = DEFAULT_MINIMIZEDINTERVAL;
        private double          _minimizedOpacity;

        // Computer sleep disable
        private bool            _sleepOff;

        // Miscellaneous
        private bool            _zoomBusy;
        private int             _testPos1;
        private int             _testPos2;
        private double          _testPos3;
        private double          _testPos4;
        private TimeSpan[]      _times;
        private Point           _movePoint;
        private bool            _disposed;

        #endregion

        // ******************************** Constructor / Dispose / Destructor / Version

        #region Public - Player Constructor

        /// <summary>
        /// Initializes a new instance of the PVS.AVPlayer.Player class.
        /// </summary>
        public Player()
        {
            _deviceId = (Global.RandomNumber.Next(10, 100000000)) + "AVP";

            _mciBuffer          = new StringBuilder(Global.BUFFER_SIZE);
            _mciSmallBuffer1    = new StringBuilder(Global.SMALL_BUFFER_SIZE);
            _mciSmallBuffer2    = new StringBuilder(Global.SMALL_BUFFER_SIZE);
            _paintCommand       = new StringBuilder(Global.SMALL_BUFFER_SIZE);

            _mciNotify = new MciNotifyClass { Player = this };

            _timer = new Timer { Interval = DEFAULT_TIMERINTERVAL };
            _timer.Tick += AV_TimerTick;

            _minimizeTimer = new Timer();
            _minimizeTimer.Tick += AV_MinimizeTimer_Tick;

            _times = new TimeSpan[2];
        }

        /// <summary>
        /// Initializes a new instance of the PVS.AVPlayer.Player class.
        /// </summary>
        /// <param name="display">The form or control that is used for displaying video.</param>
        public Player(Control display) : this(display, null) {}

        /// <summary>
        /// Initializes a new instance of the PVS.AVPlayer.Player class.
        /// </summary>
        /// <param name="display">The form or control that is used for displaying video.</param>
        /// <param name="overlay">The form that is used as display overlay.</param>
        public Player(Control display, Form overlay) : this()
        {
            if (display == null) return;
            if (AV_SetDisplay(display, true) == Global.MCIERR_NO_ERROR && overlay != null)
            {
                AV_SetOverlay(overlay);
            }
        }

        #endregion

        #region Public - Player Dispose / Destructor

        /// <summary>
        /// Remove the player and clean up any resources being used.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Remove the player and clean up any resources being used.
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                // If disposing equals true, dispose all managed and unmanaged resources.
                if (disposing)
                {
                    if (_hasDevice) AV_CloseDevice(true, true);

                    if (_fullScreen) AV_ResetFullScreen();
                    if (_sleepOff) SafeNativeMethods.SleepStatus = false;

                    // Dispose managed resources.
                    if (_hasPositionSlider) PositionSlider = null;
                    if (_speedSlider != null) SpeedSlider = null;
                    if (_shuttleSlider != null) ShuttleSlider = null;
                    if (_volumeSlider != null) AudioVolumeSlider = null;
                    if (_balanceSlider != null) AudioBalanceSlider = null;

                    _mciNotify.Dispose(); _mciNotify = null;
                    _timer.Dispose(); _timer = null;
                    _minimizeTimer.Dispose(); _minimizeTimer = null;

                    if (_hasOverlay) Overlay = null;
                    _deviceId = string.Empty;
                }
                else // called from destructor (GC)
                {
                    if (_hasDevice) SafeNativeMethods.mciSendString("close " + _deviceId, null, 0, IntPtr.Zero);
                    if (_fullScreen)
                    {
                        // if called by finalizer, everything else (main form) may already be gone
                        try { AV_ResetFullScreen(); }
                        catch { /* ignored */ }
                    }
                    if (_sleepOff) SafeNativeMethods.SleepStatus = false;
                }

                // Resources disposed.
                _disposed = true;
            }
        }

        /// <summary>
        /// Remove the player and clean up any resources being used.
        /// </summary>
        ~Player()
        {
            Dispose(false);
        }

        # endregion

        #region Public - PVS.AVPlayer Version

        /// <summary>
        /// Gets the version number of the PVS.AVPlayer library.
        /// </summary>
        public static float Version
        {
            get { return Global.VERSION; }
        }

        /// <summary>
        /// Gets the version string of the PVS.AVPlayer library.
        /// </summary>
        public static string VersionString
        {
            get { return Global.VERSION_STRING; }
        }

        #endregion


        // ******************************** Public Properties and Methods

        // ************************ Play

        #region Public - Play / Rewind / Skip / Step

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            AV_GetStartInfo();
            _siFileName = fileName;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="repeat">A value indicating whether to repeat playback when the media has finished playing.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, bool repeat)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            AV_GetStartInfo();
            _siFileName = fileName;
            _siRepeat = repeat;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="display">The form or control to use for displaying the media's video.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, Control display)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            AV_GetStartInfo();
            _siFileName = fileName;
            _siDisplay = display;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="display">The form or control to use for displaying the media's video.</param>
        /// <param name="repeat">A value indicating whether to repeat playback when the media has finished playing.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, Control display, bool repeat)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            AV_GetStartInfo();
            _siFileName = fileName;
            _siDisplay = display;
            _siRepeat = repeat;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="startPosition">The media's playback start position.</param>
        /// <param name="endPosition">The media's playback end position ((TimeSpan.Zero) 00:00:00 = natural end of the media).</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, TimeSpan startPosition, TimeSpan endPosition)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            _siFileName = fileName;
            _siDisplay = _display;
            _siStartPosition = startPosition;
            _siEndPosition = endPosition;
            _siRepeat = _repeat;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="startPosition">The media's playback start position.</param>
        /// <param name="endPosition">The media's playback end position ((TimeSpan.Zero) 00:00:00 = natural end of the media).</param>
        /// <param name="repeat">A value indicating whether to repeat playback when the media has finished playing.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, TimeSpan startPosition, TimeSpan endPosition, bool repeat)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            _siFileName = fileName;
            _siDisplay = _display;
            _siStartPosition = startPosition;
            _siEndPosition = endPosition;
            _siRepeat = repeat;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="display">The form or control to use for displaying the media's video.</param>
        /// <param name="startPosition">The media's playback start position.</param>
        /// <param name="endPosition">The media's playback end position ((TimeSpan.Zero) 00:00:00 = natural end of the media).</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, Control display, TimeSpan startPosition, TimeSpan endPosition)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            _siFileName = fileName;
            _siDisplay = display;
            _siStartPosition = startPosition;
            _siEndPosition = endPosition;
            _siRepeat = _repeat;

            return AV_Play();
        }

        /// <summary>
        /// Plays media.
        /// </summary>
        /// <param name="fileName">The path and filename of the media to play.</param>
        /// <param name="display">The form or control to use for displaying the media's video.</param>
        /// <param name="startPosition">The media's playback start position.</param>
        /// <param name="endPosition">The media's playback end position ((TimeSpan.Zero) 00:00:00 = natural end of the media).</param>
        /// <param name="repeat">A value indicating whether to repeat playback when the media has finished playing.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Play(string fileName, Control display, TimeSpan startPosition, TimeSpan endPosition, bool repeat)
        {
            if (_busyStarting) return Global.MCIERR_NO_ERROR;

            _siFileName = fileName;
            _siDisplay = display;
            _siStartPosition = startPosition;
            _siEndPosition = endPosition;
            _siRepeat = repeat;

            return AV_Play();
        }

        /// <summary>
        /// Rewinds the media's playback position to the StartPosition.
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Rewind()
        {
            if (_hasDevice) Position = TimeSpan.FromMilliseconds(_startPositionMedia);
            else _lastError = Global.MCIERR_DEVICE_NOT_READY;
            return _lastError;
        }

        /// <summary>
        /// Changes the media's playback position in any direction by the given amount of seconds.
        /// </summary>
        /// <param name="seconds">The amount of seconds to skip.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Skip(int seconds)
        {
            if (_hasDevice) Position += TimeSpan.FromSeconds(seconds);
            else _lastError = Global.MCIERR_DEVICE_NOT_READY;
            return _lastError;
        }

        /// <summary>
        /// Changes the media's playback position in any direction by the given amount of (video) frames.
        /// </summary>
        /// <param name="frames">The amount of frames to step.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Step(int frames)
        {
            if (!_playing)
            {
                _lastError = Global.MCIERR_DEVICE_NOT_READY;
                return _lastError;
            }

            int retVal = Global.MCIERR_NO_ERROR;
            if (frames != 0)
            {
                if (_hasVideo)
                {
                    int savePlayFrom;
                    if (_paused)
                    {
                        if (_resumeIsPlay)
                        {
                            _resumeIsPlay = false;
                            savePlayFrom = _startPositionMedia;
                            _startPositionMedia = PositionX;
                            AV_PlayMedia(false);
                            _startPositionMedia = savePlayFrom;
                        }
                        else
                        {
                            AV_MciSendString("resume");
                        }
                    }

                    retVal = AV_MciSendString("step", "by " + frames.ToString());
                    _resumeIsPlay = true;

                    if (retVal == Global.MCIERR_NO_ERROR)
                    {
                        if (_hasPositionSlider) AV_TimerTick(this, EventArgs.Empty);
                        else if (MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);

                        if (!_paused)
                        {
                            _resumeIsPlay = false;
                            savePlayFrom = _startPositionMedia;
                            _startPositionMedia = PositionX;
                            AV_PlayMedia(false);
                            _startPositionMedia = savePlayFrom;
                        }
                    }
                }
                else
                {
                    PositionX += (frames * 50);
                    if (_hasPositionSlider) AV_TimerTick(this, EventArgs.Empty);
                    else if (MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);
                }
                Application.DoEvents();
            }
            _lastError = retVal;
            return _lastError;
        }

        #endregion

        #region Public - Pause / Resume / PlayNext / PlayPrevious / Stop / Reset

        /// <summary>
        /// Activates the player's pause mode (pauses playback of media).
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Pause()
        {
            int retVal = Global.MCIERR_NO_ERROR;

            if (!_paused)
            {
                if (_hasDevice)
                {
                    retVal = AV_MciSendString("pause");
                    if (retVal == Global.MCIERR_NO_ERROR)
                    {
                        _paused = true;
                        if (MediaPaused != null) MediaPaused(this, EventArgs.Empty);
                        if (_timerEnabled || _hasPositionSlider) _timer.Stop();
                    }
                }
                else
                {
                    _paused = true;
                    if (MediaPaused != null) MediaPaused(this, EventArgs.Empty);
                }
            }
            _lastError = retVal;
            return retVal;
        }

        /// <summary>
        /// Deactivates the player's pause mode (resumes playback of paused media).
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Resume()
        {
            int retVal = Global.MCIERR_NO_ERROR;

            if (_paused)
            {
                if (_hasDevice)
                {
                    int currentPos = PositionX;

                    _paused = false;
                    if (_resumeIsPlay)
                    {
                        _resumeIsPlay = false;
                        int savePlayFrom = _startPositionMedia;
                        _startPositionMedia = currentPos;
                        AV_PlayMedia(false);
                        _startPositionMedia = savePlayFrom;
                    }
                    else
                    {
                        retVal = AV_MciSendString("resume");
                    }
                    if (retVal == Global.MCIERR_NO_ERROR)
                    {
                        if (!_videoEnabled)
                        {
                            _mciVideoEnabled = true;
                            AV_SetVideoEnabled(false);
                        }
                        if (MediaResumed != null) MediaResumed(this, EventArgs.Empty);
                        if (_timerEnabled || _hasPositionSlider) _timer.Start();
                    }
                }
                else
                {
                    _resumeIsPlay = false;
                    _paused = false;
                    if (MediaResumed != null) MediaResumed(this, EventArgs.Empty);
                }
            }
            _lastError = retVal;
            return retVal;
        }

        /// <summary>
        /// Requests playback of previous media (event message only and only if media is playing).
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int PlayPrevious()
        {
            if (_playing && MediaPreviousRequested != null) MediaPreviousRequested(this, EventArgs.Empty);
            _lastError = Global.MCIERR_NO_ERROR;
            return _lastError;
        }

        /// <summary>
        /// Requests playback of next media (event message only and only if media is playing).
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int PlayNext()
        {
            if (_playing && MediaNextRequested != null) MediaNextRequested(this, EventArgs.Empty);
            _lastError = Global.MCIERR_NO_ERROR;
            return _lastError;
        }

        /// <summary>
        /// Stops playback of media.
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Stop()
        {
            if (_hasDevice) AV_CloseDevice(false, true);
            _lastError = Global.MCIERR_NO_ERROR;
            return _lastError;
        }

        /// <summary>
        /// Stops media playback and resets the player's settings to their default values (except fullscreen and overlay settings).
        /// </summary>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int Reset()
        {
            Stop();

            DisplayMode = DEFAULT_DISPLAYMODE;
            AudioVolume = DEFAULT_AUDIO_VOLUME;
            AudioBalance = DEFAULT_AUDIO_BALANCE;
            AudioEnabled = DEFAULT_AUDIO_ENABLED;

            Paused = false;
            Repeat = false;
            Speed = DEFAULT_SPEED;
            StartPosition = TimeSpan.Zero;
            EndPosition = TimeSpan.Zero;

            _lastError = Global.MCIERR_NO_ERROR;
            return _lastError;
        }

        #endregion

        #region Public - Playing / Paused

        /// <summary>
        /// Gets a value indicating whether media is playing (includes state paused).
        /// </summary>
        public Boolean Playing
        {
            get  { return _playing; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's pause mode is activated.
        /// </summary>
        public Boolean Paused
        {
            get { return _paused; }
            set
            {
                if (value) Pause();
                else Resume();
            }
        }

        #endregion

        #region Public - Position

        /// <summary>
        /// Gets or sets the playing media's playback position relative (0 to 1) to the natural length (duration) of the media.
        /// </summary>
        public double PositionTrack
        {
            get
            {
                if (_mediaLength <= 0) return 0;
                return (double)PositionX / _mediaLength;
            }
            set
            {
                if (!(value >= 0) || !(value <= 1)) return;
                _testPos1 = (int)(value * _mediaLength);
                PositionX = _testPos1;

                if (!_hasPositionSlider) return;
                if (_psHandlesProgress)
                {
                    if (_testPos1 < _positionSlider.Minimum) _positionSlider.Value = _positionSlider.Minimum;
                    else if (_testPos1 > _positionSlider.Maximum) _positionSlider.Value = _positionSlider.Maximum;
                    else _positionSlider.Value = _testPos1;
                }
                else _positionSlider.Value = _testPos1;
            }
        }

        /// <summary>
        /// Gets or sets the playing media's playback position relative (0 to 1) to the start- and endposition of the media.
        /// </summary>
        public double PositionProgress
        {
            get
            {
                _testPos3 = _endPositionMedia == 0 ? _mediaLength : _endPositionMedia;
                if (_testPos3 == 0 || _testPos3 <= _startPositionMedia) return 0;

                _testPos4 = ((double)PositionX - _startPositionMedia) / (_testPos3 - _startPositionMedia);
                if (_testPos4 < 0) return 0;
                return _testPos4 > 1 ? 1 : _testPos4;
            }
            set
            {
                if (value >= 0 && value <= 1)
                {
                    _testPos3 = _endPositionMedia == 0 ? _mediaLength : _endPositionMedia;
                    if (!(_testPos3 >= _startPositionMedia)) return;
                    _testPos1 = (int)(value * (_testPos3 - _startPositionMedia)) + _startPositionMedia;
                    PositionX = _testPos1;

                    if (!_hasPositionSlider) return;
                    if (_psHandlesProgress)
                    {
                        if (_testPos1 < _positionSlider.Minimum) _positionSlider.Value = _positionSlider.Minimum;
                        else if (_testPos1 > _positionSlider.Maximum) _positionSlider.Value = _positionSlider.Maximum;
                        else _positionSlider.Value = _testPos1;
                    }
                    else _positionSlider.Value = _testPos1;
                }
            }
        }

        /// <summary>
        /// Gets or sets the playing media's playback position.
        /// </summary>
        public TimeSpan Position
        {
            get { return TimeSpan.FromMilliseconds(PositionX); }
            set
            {
                _testPos1 = (int)value.TotalMilliseconds;
                PositionX = _testPos1;
                if (!_hasPositionSlider) return;
                if (_psHandlesProgress)
                {
                    if (_testPos1 < _positionSlider.Minimum) _positionSlider.Value = _positionSlider.Minimum;
                    else if (_testPos1 > _positionSlider.Maximum) _positionSlider.Value = _positionSlider.Maximum;
                    else _positionSlider.Value = _testPos1;
                }
                else _positionSlider.Value = _testPos1;
            }
        }

        // Position helper function
        private int PositionX
        {
            get
            {
                if (_hasDevice)
                {
                    if (_psTracking) return _positionSlider.Value;
                    _lastError = SafeNativeMethods.mciSendString("status " + _deviceId + " position", _mciSmallBuffer2, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if (_lastError == Global.MCIERR_NO_ERROR)
                    {
                        int number = 0;
                        for (int i = 0; i < _mciSmallBuffer2.Length; ++i)
                        {
                            number = 10 * number + (_mciSmallBuffer2[i] - 48);
                        }
                        return number;
                    }
                }
                else
                {
                    _lastError = Global.MCIERR_NO_ERROR;
                }
                return 0;
            }
            set
            {
                if (_hasDevice)
                {
                    if (_seeking)
                    {
                        _seekSkipped = true;
                        _seekSkippedPosition = value;
                    }
                    else
                    {
                        _seeking = true;
                        if (_timerEnabled && (!_hasPositionSlider)) _timer.Stop();

                        do
                        {
                            _seekSkipped = false;

                            if (value < 0) value = 0;
                            else if (value >= _mediaLength) value = _mediaLength - 100;

                            if (value == 0) AV_MciSendString("seek", "to start");

                            _mciBuffer.Length = 0;
                            _mciBuffer.Append("play ").Append(_deviceId);
                            if (value > 0)
                            {
                                _mciBuffer.Append(" from ").Append(value);
                                if (_endPositionMedia > value) _mciBuffer.Append(" to ").Append(_endPositionMedia);
                            }
                            else if (_endPositionMedia > 0)
                            {
                                _mciBuffer.Append(" to ").Append(_endPositionMedia);
                            }
                            _mciBuffer.Append(" notify");

                            _resumeIsPlay = false;

                            SafeNativeMethods.mciSendString(_mciBuffer.ToString(), null, 0, _mciNotify.Handle);
                            if (_paused) AV_MciSendString("pause");

                            Application.DoEvents();

                            if (_lastError == Global.MCIERR_NO_ERROR && MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);

                            value = _seekSkippedPosition;

                        } while (_seekSkipped);

                        if (_timerEnabled && (!_hasPositionSlider)) _timer.Start();
                        _seeking = false;
                    }
                }
                else
                {
                    _lastError = Global.MCIERR_NO_ERROR;
                }
            }
        }

        /// <summary>
        /// Gets or sets the player's media playback startposition for the next media to play.
        /// When set and the next media starts playing, the value is copied to
        /// the player's StartPositionMedia setting and then reset to 00:00:00.
        /// </summary>
        public TimeSpan StartPosition
        {
            get { return TimeSpan.FromMilliseconds(_startPosition); }
            set
            {
                if (_startPosition != (int)value.TotalMilliseconds)
                {
                    _startPosition = (int)value.TotalMilliseconds;
                    if (MediaStartEndChanged != null) MediaStartEndChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets or sets the playing media's playback startposition.
        /// </summary>
        public TimeSpan StartPositionMedia
        {
            get { return TimeSpan.FromMilliseconds(_startPositionMedia); }
            set
            {
                if (!_hasDevice) return;
                if (_startPositionMedia == (int) value.TotalMilliseconds) return;

                double pos = PositionX;
                int endpos = _endPositionMedia == 0 ? _mediaLength : _endPositionMedia;
                bool repeatSet = false;

                if (value.TotalMilliseconds < endpos)
                {
                    _startPositionMedia = (int)value.TotalMilliseconds;
                    if (_hasPositionSlider && _psHandlesProgress) _positionSlider.Minimum = _startPositionMedia;

                    if (_startPositionMedia > pos || (_endPositionMedia != 0 && pos > _endPositionMedia))
                    {
                        if (!_repeat)
                        {
                            _repeat = true;
                            repeatSet = true;
                        }
                        AV_EndOfMedia(false);
                        if (_paused)
                        {
                            AV_MciSendString("pause");
                            AV_TimerTick(this, EventArgs.Empty);
                        }
                        if (repeatSet) _repeat = false;
                    }
                }
                if (MediaStartEndMediaChanged != null) MediaStartEndMediaChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets the player's media playback endposition for the next media to play.
        /// When set and the next media starts playing, the value is copied to the player's EndPositionMedia setting
        /// and then reset to 00:00:00. TimeSpan.Zero or 00:00:00 = natural end of media.
        /// </summary>
        public TimeSpan EndPosition
        {
            get { return TimeSpan.FromMilliseconds(_endPosition); }
            set
            {
                if (_endPosition != (int)value.TotalMilliseconds)
                {
                    _endPosition = (int)value.TotalMilliseconds;
                    if (MediaStartEndChanged != null) MediaStartEndChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets or sets the playing media's playback endposition.
        /// TimeSpan.Zero or 00:00:00 = natural end of media.
        /// </summary>
        public TimeSpan EndPositionMedia
        {
            get { return TimeSpan.FromMilliseconds(_endPositionMedia); }
            set
            {
                if (_hasDevice)
                {
                    if (_endPositionMedia == (int)value.TotalMilliseconds) return;
                    double pos = PositionX;
                    int newPos = value.TotalMilliseconds == 0 ? _mediaLength : (int)value.TotalMilliseconds;
                    bool resetPos = false;
                    bool resetRepeat = false;

                    if (newPos > _mediaLength) newPos = _mediaLength;
                    if (newPos > _startPositionMedia)
                    {
                        _endPositionMedia = newPos;
                        if (_hasPositionSlider && _psHandlesProgress) _positionSlider.Maximum = _endPositionMedia;

                        if (_endPositionMedia > pos)
                        {
                            resetPos = true;
                            if (!_repeat)
                            {
                                _repeat = true;
                                resetRepeat = true;
                            }
                        }

                        if (_repeat)
                        {
                            AV_EndOfMedia(false);
                            if (_paused)
                            {
                                AV_MciSendString("pause");
                                AV_TimerTick(this, EventArgs.Empty);
                            }
                            if (resetPos)
                            {
                                Position = TimeSpan.FromMilliseconds(pos);
                                if (resetRepeat) _repeat = false;
                            }
                        }
                    }
                    if (MediaStartEndMediaChanged != null) MediaStartEndMediaChanged(this, EventArgs.Empty);
                }
                else _endPosition = 0;
            }
        }

        #endregion

        #region  Public - Speed / Repeat

        /// <summary>
        /// Gets or sets a value indicating the player's media playback speed (normal speed = 1000).
        /// </summary>
        public int Speed
        {
            get { return _speed; }
            set
            {
                if (value < 10) value = 10;
                AV_SetSpeed(value);
                if (_speedSlider != null) SpeedSlider_ValueToSlider(value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to repeat media playback when finished.
        /// </summary>
        public bool Repeat
        {
            get { return _repeat; }
            set
            {
                if (_repeat == value) return;

                _repeat = value;
                if (_hasDevice && _repeat)
                {
                    double pos = PositionX;
                    if ((pos < _startPositionMedia || (_endPositionMedia != 0 && pos > _endPositionMedia)))
                    {
                        AV_EndOfMedia(true);
                        if (_paused)
                        {
                            AV_MciSendString("pause");
                            AV_TimerTick(this, EventArgs.Empty);
                        }
                    }
                }
                if (MediaRepeatChanged != null) MediaRepeatChanged(this, EventArgs.Empty);
            }
        }

        #endregion

        // ************************ Video

        #region Public - Video

        /// <summary>
        /// Gets a value indicating whether the playing media contains video.
        /// </summary>
        public bool VideoPresent
        {
            get { return _hasVideo; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's video display is enabled.
        /// </summary>
        public bool VideoEnabled
        {
            get { return _videoEnabled; }
            set { AV_SetVideoEnabled(value); }
        }

        /// <summary>
        /// Gets or sets the size and position of the video image on the player's display. When set, the player's displaymode is set to Displaymode.Manual.
        /// </summary>
        public Rectangle VideoBounds
        {
            get { return _videoBounds; }
            set
            {
                if (_hasDisplay)
                {
                    if ((value.Width >= 10) && (value.Height >= 10))
                    {
                        _videoBounds = value;
                        _hasVideoBounds = true;

                        if (_displayMode == DisplayMode.Manual) _display.Refresh();
                        else DisplayMode = DisplayMode.Manual;

                        if (MediaVideoBoundsChanged != null) MediaVideoBoundsChanged(this, EventArgs.Empty);
                    }
                    else _lastError = Global.MCIERR_OUTOFRANGE;
                }
                else _lastError = Global.MCIERR_NO_WINDOW;
            }
        }

        /// <summary>
        /// Gets the original size of the playing video.
        /// </summary>
        public Size VideoSourceSize
        {
            get { return _hasVideoSourceSize ? _videoSourceSize : Size.Empty; }
        }

        /// <summary>
        /// Gets the nominal video frame rate of the playing media. The units are in frames per second multiplied by 1000.
        /// </summary>
        public int FrameRate
        {
            get
            {
                if (_playing && _hasVideo)
                {
                    _lastError = SafeNativeMethods.mciSendString("status " + _deviceId + " nominal frame rate", _mciSmallBuffer2, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if (_lastError == Global.MCIERR_NO_ERROR)
                    {
                        int number = 0;
                        for (int i = 0; i < _mciSmallBuffer2.Length; ++i)
                        {
                            number = 10 * number + (_mciSmallBuffer2[i] - 48);
                        }
                        return number;
                    }
                }
                else _lastError = Global.MCIERR_NO_ERROR;
                return 0;
            }
        }

        /// <summary>
        /// Enlarges or reduces the size of the video image of the playing media at the display's center location.
        /// </summary>
        /// <param name="factor">The factor to zoom the video image with.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoZoom(double factor)
        {
            return VideoZoom(factor, _display.Width / 2, _display.Height / 2);
        }

        /// <summary>
        /// Enlarges or reduces the size of the video image of the playing media at the specified display location.
        /// </summary>
        /// <param name="factor">The factor to zoom the video image with.</param>
        /// <param name="center">The player's display zoom center point location.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoZoom(double factor, Point center)
        {
            return (VideoZoom(factor, center.X, center.Y));
        }

        /// <summary>
        /// Enlarges or reduces the size of the video image of the playing media at the specified display location.
        /// </summary>
        /// <param name="factor">The factor to zoom the video image with.</param>
        /// <param name="xCenter">The player's display zoom horizontal (x) center location.</param>
        /// <param name="yCenter">The player's display zoom vertical (y) center location.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoZoom(double factor, int xCenter, int yCenter)
        {
            if (_hasVideo && factor > 0)
            {
                _lastError = Global.MCIERR_NO_ERROR;

                if (factor != 1)
                {
                    if (_zoomBusy) return _lastError;

                    _zoomBusy = true;
                    double width = 0;
                    double height = 0;
                    Rectangle r = new Rectangle(_videoBounds.Location, _videoBounds.Size);

                    if (r.Width < r.Height)
                    {
                        r.X = (int)Math.Round(-factor * (xCenter - r.X)) + xCenter;
                        width = r.Width * factor;

                        if (width >= 10)
                        {
                            r.Y = (int)Math.Round(-(width / r.Width) * (yCenter - r.Y)) + yCenter;
                            height = (width / r.Width) * r.Height;
                        }
                    }
                    else
                    {
                        r.Y = (int)Math.Round(-factor * (yCenter - r.Y)) + yCenter;
                        height = r.Height * factor;

                        if (height >= 10)
                        {
                            r.X = (int)Math.Round(-(height / r.Height) * (xCenter - r.X)) + xCenter;
                            width = (height / r.Height) * r.Width;
                        }
                    }

                    r.Width = (int)Math.Round(width);
                    r.Height = (int)Math.Round(height);
                    VideoBounds = r;

                    _zoomBusy = false;
                }
            }
            else _lastError = Global.MCIERR_DEVICE_NOT_READY;
            return _lastError;
        }

        /// <summary>
        /// Enlarges the specified part of the player's display to the player's entire display.
        /// </summary>
        /// <param name="area">The area of the player's display to enlarge.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoZoom(Rectangle area)
        {
            if (_hasVideo)
            {
                if ((area.X >= 0 && area.X <= (_display.Width - 8)) && (area.Y >= 0 && area.Y <= (_display.Height - 8)) && (area.X + area.Width <= _display.Width) && (area.Y + area.Height <= _display.Height))
                {
                    double factorX = (double)_display.Width / area.Width;
                    double factorY = (double)_display.Height / area.Height;

                    //Rectangle r = new Rectangle(
                    //    (int) ((_videoBounds.X - area.X) * factorX),
                    //    (int)((_videoBounds.Y - area.Y) * factorY),
                    //    (int)(_videoBounds.Width * factorX),
                    //    (int)(_videoBounds.Height * factorY));

                    //VideoBounds = r;

                    VideoBounds = new Rectangle(
                        (int)((_videoBounds.X - area.X) * factorX),
                        (int)((_videoBounds.Y - area.Y) * factorY),
                        (int)(_videoBounds.Width * factorX),
                        (int)(_videoBounds.Height * factorY));
                }
                else _lastError = Global.MCIERR_OUTOFRANGE;
            }
            else _lastError = Global.MCIERR_DEVICE_NOT_READY;
            return _lastError;
        }

        /// <summary>
        /// Moves the position of the video image of the playing media by the given amount of pixels.
        /// </summary>
        /// <param name="dx">The amount of pixels to move the video image in the horizontal (x) direction.</param>
        /// <param name="dy">The amount of pixels to move the video image in the vertical (y) direction.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoMove(int dx, int dy)
        {
            VideoBounds = new Rectangle(_videoBounds.X + dx, _videoBounds.Y + dy, _videoBounds.Width, _videoBounds.Height);
            return _lastError;
        }

        /// <summary>
        /// Enlarges or reduces the size of the video image of the playing media by the given amount of pixels at the center of the video image.
        /// </summary>
        /// <param name="dx">The amount of pixels to stretch the video image in the horizontal (x) direction.</param>
        /// <param name="dy">The amount of pixels to stretch the video image in the vertical (y) direction.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int VideoStretch(int dx, int dy)
        {
            VideoBounds = new Rectangle(_videoBounds.X - (dx / 2), _videoBounds.Y - (dy / 2), _videoBounds.Width + dx, _videoBounds.Height + dy);
            return _lastError;
        }

        #endregion

        // ************************ Audio

        #region Public - Audio

        /// <summary>
        /// Gets a value indicating whether the playing media contains audio.
        /// </summary>
        public bool AudioPresent
        {
            get { return _hasAudio; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's audio is enabled.
        /// </summary>
        public bool AudioEnabled
        {
            get { return _audioEnabled; }
            set { AV_SetAudioEnabled(value); }
        }

        /// <summary>
        /// Gets or sets the player's audio volume, value 0 (mute) to 1000 (max).
        /// </summary>
        public int AudioVolume
        {
            get
            {
                // setting volume to 0 gives error (?) x64 only (?)
                return _audioVolume < 2 ? 0 : _audioVolume;
            }
            set
            {
                AV_SetAudioVolume(value);
                if (_volumeSlider != null) _volumeSlider.Value = _audioVolume;
            }
        }

        /// <summary>
        /// Gets or sets the player's audio balance, value 0 (left) to 1000 (right).
        /// </summary>
        public int AudioBalance
        {
            get { return _audioBalance; }
            set
            {
                AV_SetAudioBalance(value, false);
                if (_balanceSlider != null) _balanceSlider.Value = _audioBalance;
            }
        }

        #endregion

        // ************************ Display

        #region Public - Display / VideoBounds / VideoSourceSize

        /// <summary>
        /// Gets or sets the form or control that is used by the player to display video and overlays.
        /// </summary>
        public Control Display
        {
            get { return _display; }
            set { AV_SetDisplay(value, true); }
        }

        /// <summary>
        /// Gets or sets the display mode (size and position) of the video image on the player's display.
        /// </summary>
        public DisplayMode DisplayMode
        {
            get  { return _displayMode; }
            set
            {
                if (_displayMode == value) return;

                _displayMode = value;
                if (value == DisplayMode.Manual)
                {
                    if (!_hasVideoBounds)
                    {
                        _videoBounds.X = _videoBounds.Y = 0;
                        _videoBounds.Size = _display.Size;
                        _hasVideoBounds = true;
                    }
                }
                else _hasVideoBounds = false;

                if (_hasVideo) AV_SetDisplayMode(value);
                if (MediaDisplayModeChanged != null) MediaDisplayModeChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's display parent form is redrawn with displaymodes Normal, Center or Manual when the player's display is resized.
        /// </summary>
        public bool ResizeFormRefresh
        {
            get { return _resizeFormRefresh; }
            set { _resizeFormRefresh = value; }
        }

        #endregion

        #region Public - Display Overlay

        /// <summary>
        /// Gets or sets the player's display overlay.
        /// </summary>
        public Form Overlay
        {
            get { return _overlay; }
            set { AV_SetOverlay(value); }
        }

        /// <summary>
        /// Gets or sets the display mode (size and position) of the player's display overlay.
        /// </summary>
        public OverlayMode OverlayMode
        {
            get { return _overlayMode; }
            set
            {
                if (value == _overlayMode) return;

                _overlayMode = value;
                if (_hasDisplay && _hasOverlayShown) _display.Invalidate();
                if (MediaOverlayModeChanged != null) MediaOverlayModeChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's display overlay is always shown.
        /// </summary>
        public bool OverlayHold
        {
            get { return _overlayHold; }
            set
            {
                if (value == _overlayHold) return;

                if (_hasOverlay)
                {
                    if (value)
                    {
                        if (_hasDisplay && !_hasOverlayShown && _display.FindForm().Visible) AV_ShowOverlay();
                    }
                    else if (_hasOverlayShown && (!_hasDevice)) AV_HideOverlay();
                }
                _overlayHold = value;
                if (MediaOverlayHoldChanged != null) MediaOverlayHoldChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the player's display overlay can be activated for input and selection.
        /// </summary>
        public bool OverlayCanFocus
        {
            get { return _overlayCanFocus; }
            set
            {
                if (value != _overlayCanFocus) AV_SetOverlayCanFocus(value);
            }
        }

        #endregion

        #region Public - Display Overlay Delay

        /// <summary>
        /// Gets or sets the number of milliseconds a display overlay's visibility is delayed when restoring a minimized display (form). Default value: 300 ms. Set to 0 to disable.
        /// </summary>
        public int OverlayDelay
        {
            get { return _minimizedInterval; }
            set
            {
                if (value == 0)
                {
                    _minimizedInterval = 0;
                    _minimizeEnabled = false;
                    AV_MinimizeActivate(false);
                }
                else
                {
                    if (value < 100) value = 100;
                    else if (value > 1500) value = 1500;
                    _minimizedInterval = value;
                    _minimizeEnabled = true;
                    AV_MinimizeActivate(true);
                }
            }
        }

        // Overlay Delay helper functions

        private void AV_MinimizeTimer_Tick(object sender, EventArgs e)
        {
            _minimizeTimer.Stop();
            if (_hasOverlay)
            {
                if (_minimized)
                {
                    _minimizedOpacity = Overlay.Opacity;
                    _overlay.Opacity = 0;
                }
                else
                {
                    _overlay.Opacity = _minimizedOpacity;
                }
            }
        }

        private void AV_Minimize_SizeChanged(object sender, EventArgs e)
        {
            if (_minimized && _overlay.Owner.WindowState != FormWindowState.Minimized)
            {
                _minimized = false;
                _minimizeTimer.Interval = _minimizedInterval;
                _minimizeTimer.Start();
            }
            else if (_hasOverlay && _overlay.Owner.WindowState == FormWindowState.Minimized)
            {
                _minimized = true;
                _minimizeTimer.Interval = 300;
                _minimizeTimer.Start();
            }
        }

        private void AV_MinimizeActivate(bool active)
        {
            if (active)
            {
                if (!_minimizeEnabled || !_hasOverlay || _overlay.Owner == null) return;

                if (!_minimizeHasEvent)
                {
                    _overlay.Owner.SizeChanged += AV_Minimize_SizeChanged;
                    _minimizeHasEvent = true;
                }
                if (!_minimized && _overlay.Owner.WindowState == FormWindowState.Minimized)
                {
                    _minimizedOpacity = _overlay.Opacity;
                    _overlay.Opacity = 0;
                    _minimized = true;
                }
            }
            else
            {
                if (!_minimizeHasEvent) return;

                if (_overlay.Owner != null) _overlay.Owner.SizeChanged -= AV_Minimize_SizeChanged;
                _minimizeHasEvent = false;
                if (_minimized)
                {
                    _overlay.Opacity = _minimizedOpacity;
                    _minimized = false;
                }
            }
        }

        #endregion

        #region Public - FullScreen / FormRestoreBounds

        /// <summary>
        /// Gets or sets a value indicating whether the player's fullscreen mode is active.
        /// </summary>
        public bool FullScreen
        {
            get { return _fullScreen; }
            set { AV_SetFullScreen(value); }
        }

        /// <summary>
        /// Gets or sets the player's fullscreen display mode.
        /// </summary>
        public FullScreenMode FullScreenMode
        {
            get { return _fullScreenMode; }
            set
            {
                if (_fullScreen) AV_SetFullScreenMode(value, true);
                else
                {
                    _lastError = Global.MCIERR_NO_ERROR;
                    _fullScreenMode = value;
                    if (MediaFullScreenModeChanged != null) MediaFullScreenModeChanged(this, EventArgs.Empty);
                }
            }
        }

        /// <summary>
        /// Gets the location and size of the player's parent form in it's normal window state.
        /// </summary>
        public Rectangle FormRestoreBounds
        {
            get
            {
                _lastError = Global.MCIERR_NO_ERROR;
                if (_fullScreen) return _fsFormBounds;
                if (_hasDisplay)
                {
                    Form f = _display.FindForm();
                    return f.WindowState == FormWindowState.Normal ? f.Bounds : f.RestoreBounds;
                }
                _lastError = Global.MCIERR_NO_WINDOW;
                return Rectangle.Empty;
            }
        }

        #endregion

        #region Public - Sleep Mode

        /// <summary>
        /// Gets or sets a value indicating whether the System Energy Saving setting (sleep mode) is (temporary) disabled. Global (all players): System setting is restored only when all players re-enable sleep mode (after having disabled it).
        /// </summary>
        public bool SleepDisabled
        {
            get { return _sleepOff; }
            set
            {
                if (value != _sleepOff)
                {
                    SafeNativeMethods.SleepStatus = _sleepOff = value;
                }
            }
        }

        #endregion

        // ************************ ScreenCopy

        #region Public - ScreenCopy

        /// <summary>
        /// Gets or sets a value indicating the part of the screen to copy with the Player.ScreenCopy methods.
        /// </summary>
        public ScreenCopyMode ScreenCopyMode
        {
            get { return _screenCopyMode; }
            set { _screenCopyMode = value; }
        }

        /// <summary>
        /// Gets an image from (part of) the screen.
        /// </summary>
        public Image ScreenCopy
        {
            get
            {
                Bitmap memoryImage = null;

                if (_hasDisplay && (_hasVideo || _hasOverlay))
                {
                    Rectangle r;

                    switch (_screenCopyMode)
                    {
                        case ScreenCopyMode.Display:
                            r = _display.RectangleToScreen(_display.DisplayRectangle);
                            break;
                        case ScreenCopyMode.Form:
                            r = _display.FindForm().RectangleToScreen(_display.FindForm().DisplayRectangle);
                            break;
                        case ScreenCopyMode.Parent:
                            r = _display.Parent.RectangleToScreen(_display.Parent.DisplayRectangle);
                            break;
                        case ScreenCopyMode.Screen:
                            r = Screen.GetBounds(_display);
                            break;
                        //case ScreenCopyMode.Video:
                        default:
                            r = _display.RectangleToScreen(_display.DisplayRectangle);
                            if (_hasVideo) r = _display.RectangleToScreen(Rectangle.Intersect(_display.DisplayRectangle, _videoBounds));
                            break;
                    }

                    try
                    {
                        memoryImage = new Bitmap(r.Width, r.Height);
                        Graphics memoryGraphics = Graphics.FromImage(memoryImage);
                        memoryGraphics.CopyFromScreen(r.Location.X, r.Location.Y, 0, 0, r.Size);
                        memoryGraphics.Dispose();
                        _lastError = Global.MCIERR_NO_ERROR;
                    }
                    catch
                    {
                        if (memoryImage != null) { memoryImage.Dispose(); memoryImage = null; }
                        _lastError = Global.MCIERR_INTERNAL;
                    }
                }
                else
                {
                    _lastError = Global.MCIERR_NO_WINDOW;
                }
                return memoryImage;
            }
        }

        /// <summary>
        /// Copies an image from (part of) the screen to the system's clipboard.
        /// </summary>
        public int ScreenCopyToClipboard()
        {
            Image theImage = ScreenCopy;
            if (_lastError == Global.MCIERR_NO_ERROR)
            {
                try { Clipboard.SetImage(theImage); }
                catch { _lastError = Global.MCIERR_INTERNAL; }
                theImage.Dispose();
            }
            return _lastError;
        }

        /// <summary>
        /// Saves an image from (part of) the screen to the specified file.
        /// </summary>
        /// <param name="fileName">The name of the file to save.</param>
        /// <param name="imageFormat">The file format of the image to save.</param>
        public int ScreenCopyToFile(string fileName, System.Drawing.Imaging.ImageFormat imageFormat)
        {
            if ((fileName != null) && (fileName.Length > 3))
            {
                Image theImage = ScreenCopy;
                if (_lastError == Global.MCIERR_NO_ERROR)
                {
                    try { theImage.Save(fileName, imageFormat); }
                    catch { _lastError = Global.MCIERR_INTERNAL; }
                    theImage.Dispose();
                }
            }
            else
            {
                _lastError = Global.MCIERR_FILENAME_REQUIRED;
            }
            return _lastError;
        }

        #endregion

        // ************************ Various Information

        #region Public - Error Information

        /// <summary>
        /// Gets a value indicating whether an error has occured with the last player instruction.
        /// </summary>
        public bool LastError
        {
            get { return _lastError != 0; }
        }

        /// <summary>
        /// Gets the code of the player's last error that has occured (0 = no error).
        /// </summary>
        public int LastErrorCode
        {
            get { return _lastError; }
        }

        /// <summary>
        /// Gets a description of the player's last error that has occured.
        /// </summary>
        public string LastErrorString
        {
            get { return GetErrorString(_lastError); }
        }

        /// <summary>
        /// Returns a description of the specified errorcode.
        /// </summary>
        /// <param name="errorCode">The errorcode to get the description of.</param>
        public string GetErrorString(int errorCode)
        {
            SafeNativeMethods.mciGetErrorString(errorCode, _mciBuffer, Global.BUFFER_SIZE);
            return _mciBuffer.ToString();
        }

        #endregion

        #region Public - GetMediaLength / GetTrackTimes / GetProgressTimes

        /// <summary>
        /// Returns the length (duration time) of the specified part of the playing media.
        /// </summary>
        /// <param name="part">Specifies the part of the playing media to get the length (duration) of.</param>
        public TimeSpan GetMediaLength(MediaLength part)
        {
            if (!_hasDevice) return TimeSpan.Zero;

            switch (part)
            {
                case MediaLength.Total:
                    return TimeSpan.FromMilliseconds(_mediaLength);
                //break;

                case MediaLength.StartToEndPosition:
                    return _endPositionMedia == 0 ? TimeSpan.FromMilliseconds(_mediaLength - _startPositionMedia) : TimeSpan.FromMilliseconds(_endPositionMedia - _startPositionMedia);
                //break;

                case MediaLength.FromStartPosition:
                    return TimeSpan.FromMilliseconds(PositionX - _startPositionMedia);
                //break;

                case MediaLength.ToEnd:
                    return TimeSpan.FromMilliseconds(_mediaLength - PositionX);
                //break;

                case MediaLength.ToEndPosition:
                    return _endPositionMedia == 0 ? TimeSpan.FromMilliseconds(_mediaLength - PositionX) : TimeSpan.FromMilliseconds(_endPositionMedia - PositionX);
                //break;

                //case MediaLength.FromStart:
                default:
                    return (TimeSpan.FromMilliseconds(PositionX));
                //break;
            }
        }

        /// <summary>
        /// Returns the track length (duration time) of the current playback position and the time to the natural end of the playing media.
        /// TimeSpan[0] = time from natural beginning of media, TimeSpan[1] = time to natural end of media.
        /// </summary>
        public TimeSpan[] GetTrackTimes()
        {
            if (_hasDevice)
            {
                int pos = PositionX;
                _times[0] = TimeSpan.FromMilliseconds(pos);
                _times[1] = TimeSpan.FromMilliseconds(_mediaLength - pos);
            }
            else 
            {
                _times[0] = _times[1] = TimeSpan.Zero;
            }
            return _times;
        }

        /// <summary>
        /// Returns the progress length (duration time) of the current playback position and the time to the endposition of the playing media.
        /// TimeSpan[0] = time from playback startposition of media, TimeSpan[1] = time to playback endposition of media.
        /// </summary>
        /// <param name="replace">Specifies whether negative values are replaced by (positive) track lengths.</param>
        public TimeSpan[] GetProgressTimes(bool replace)
        {
            if (_hasDevice)
            {
                int pos = PositionX;
                if (replace)
                {
                    if (_startPositionMedia == 0 || pos < _startPositionMedia) _times[0] = TimeSpan.FromMilliseconds(pos);
                    else _times[0] = TimeSpan.FromMilliseconds(pos - _startPositionMedia);

                    if (_endPositionMedia == 0 || pos > _endPositionMedia) _times[1] = TimeSpan.FromMilliseconds(_mediaLength - pos);
                    else _times[1] = TimeSpan.FromMilliseconds(_endPositionMedia - pos);
                }
                else
                {
                    _times[0] = TimeSpan.FromMilliseconds(pos - _startPositionMedia);
                    if (_endPositionMedia == 0) _times[1] = TimeSpan.FromMilliseconds(_mediaLength - pos);
                    else _times[1] = TimeSpan.FromMilliseconds(_endPositionMedia - pos);
                }
            }
            else
            {
                _times[0] = _times[1] = TimeSpan.Zero;
            }
            return _times;
        }

        // GetLength helper function
        private int Length
        {
            get
            {
                if (_hasMediaLength) return _mediaLength;

                if (_hasDevice)
                {
                    _lastError = SafeNativeMethods.mciSendString("status " + _deviceId + " length", _mciSmallBuffer1, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if (_lastError == Global.MCIERR_NO_ERROR)
                    {
                        _mediaLength = 0;
                        for (int i = 0; i < _mciSmallBuffer1.Length; ++i)
                        {
                            _mediaLength = 10 * _mediaLength + (_mciSmallBuffer1[i] - 48);
                        }
                        _hasMediaLength = true;
                        return _mediaLength;
                    }
                }
                return 0;
            }
        }

        #endregion

        #region Public - GetMediaName

        /// <summary>
        /// Returns (part of) the filename of the playing media.
        /// </summary>
        /// <param name="part">Specifies the part of the filename to return.</param>
        public string GetMediaName(MediaName part)
        {
            string mediaName = string.Empty;

            if (_hasDevice)
            {
                try
                {
                    switch (part)
                    {
                        case MediaName.FileName:
                            mediaName = System.IO.Path.GetFileName(_fileName);
                            break;
                        case MediaName.DirectoryName:
                            mediaName = System.IO.Path.GetDirectoryName(_fileName);
                            break;
                        case MediaName.PathRoot:
                            mediaName = System.IO.Path.GetPathRoot(_fileName);
                            break;
                        case MediaName.Extension:
                            mediaName = System.IO.Path.GetExtension(_fileName);
                            break;
                        case MediaName.FileNameWithoutExtension:
                            mediaName = System.IO.Path.GetFileNameWithoutExtension(_fileName);
                            break;

                        default: // case MediaName.FullPath:
                            mediaName = _fileName;
                            break;
                    }
                }
                catch { /* ignored */ }
            }
            return mediaName;
        }

        #endregion

        // ******************************** MCI Info / MCI Direct Access

        #region Public - MCI Info / MCI Direct Access

        // ************************ MCI Info

        /// <summary>
        /// Gets the ID of the MCI device associated with the playing media.
        /// </summary>
        public string MciDeviceId
        {
            get
            {
                return _hasDevice ? _deviceId : string.Empty;
            }
        }

        /// <summary>
        /// Gets the type name of the MCI device associated with the playing media.
        /// </summary>
        public string MciDeviceType
        {
            get
            {
                if (_hasDevice)
                {
                    int result = SafeNativeMethods.mciSendString("capability " + _deviceId + " device type", _mciSmallBuffer1, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if (result == Global.MCIERR_NO_ERROR) return _mciSmallBuffer1.ToString();
                }
                return string.Empty;
            }
        }

        /// <summary>
        /// Gets a description (product name) of the MCI device associated with the playing media.
        /// </summary>
        public string MciDeviceName
        {
            get
            {
                if (_hasDevice)
                {
                    int result = SafeNativeMethods.mciSendString("info " + _deviceId + " product", _mciSmallBuffer1, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if (result == Global.MCIERR_NO_ERROR) return _mciSmallBuffer1.ToString();
                }
                return string.Empty;
            }
        }

        // ************************ MCI Direct Access

        /// <summary>
        /// Sends a command to the MCI device associated with the playing media.
        /// </summary>
        /// <param name="command">String that specifies the MCI command.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int MciCommand(string command)
        {
            _lastError = _hasDevice ? SafeNativeMethods.mciSendString(command.Trim() + " " + _deviceId, null, 0, IntPtr.Zero) : Global.MCIERR_DEVICE_NOT_READY;
            return _lastError;
        }

        /// <summary>
        /// Sends a command to the MCI device associated with the playing media.
        /// </summary>
        /// <param name="command">String that specifies the MCI command.</param>
        /// <param name="parameters">String that specifies the MCI command parameters.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int MciCommand(string command, string parameters)
        {
            if (_hasDevice)
            {
                parameters = parameters.Trim();
                _lastError = parameters.Length == 0 ? SafeNativeMethods.mciSendString(command.Trim() + " " + _deviceId, null, 0, IntPtr.Zero) : SafeNativeMethods.mciSendString(command.Trim() + " " + _deviceId + " " + parameters, null, 0, IntPtr.Zero);
            }
            else
            {
                _lastError = Global.MCIERR_DEVICE_NOT_READY;
            }
            return _lastError;
        }

        /// <summary>
        /// Sends a request to the MCI device associated with the playing media.
        /// </summary>
        /// <param name="request">String that specifies the MCI request.</param>
        /// <param name="parameters">String that specifies the MCI request parameters.</param>
        /// <param name="result">A string that receives return information.</param>
        /// <returns>Returns 0 if successful or an MCI errorcode otherwise (also available with LastErrorCode).</returns>
        public int MciRequest(string request, string parameters, out string result)
        {
            result = string.Empty;
            if (_hasDevice)
            {
                _lastError = SafeNativeMethods.mciSendString(request.Trim() + " " + _deviceId + " " + parameters.Trim(), _mciBuffer, Global.BUFFER_SIZE, IntPtr.Zero);
                if (_lastError == Global.MCIERR_NO_ERROR)
                {
                    result = _mciBuffer.ToString();
                }
            }
            else
            {
                _lastError = Global.MCIERR_DEVICE_NOT_READY;
            }
            return _lastError;
        }

        #endregion

        // ******************************** Show System Control Panels

        #region Public - Show System Control Panels

        // non-static methods by design

        /// <summary>
        /// Opens the System Audio Mixer Control Panel for user access to the system audio mixer settings.
        /// </summary>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowAudioMixerPanel()
        {
            return ShowAudioMixerPanel(null);
        }

        /// <summary>
        /// Opens the System Audio Mixer Control Panel for user access to the system audio mixer settings.
        /// </summary>
        /// <param name="centerForm">The control panel is centered on top of the specified form.</param>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowAudioMixerPanel(Form centerForm)
        {
            return SafeNativeMethods.CenterSystemDialog("SndVol.exe", "", centerForm) || SafeNativeMethods.CenterSystemDialog("SndVol32.exe", "", centerForm);
        }

        /// <summary>
        /// Opens the System Sound Control Panel for user access to the default system audio output settings.
        /// </summary>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowAudioOutputPanel()
        {
            return ShowAudioOutputPanel(null);
        }

        /// <summary>
        /// Opens the System Sound Control Panel for user access to the default system audio output settings.
        /// </summary>
        /// <param name="centerForm">The control panel is centered on top of the specified form.</param>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowAudioOutputPanel(Form centerForm)
        {
            return SafeNativeMethods.CenterSystemDialog("control", "mmsys.cpl,,0", centerForm);
        }

        /// <summary>
        /// Opens the System Display Control Panel for user access to the system display settings.
        /// </summary>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowDisplaySettingsPanel()
        {
            return ShowDisplaySettingsPanel(null);
        }

        /// <summary>
        /// Opens the System Display Control Panel for user access to the system display settings.
        /// </summary>
        /// <param name="centerForm">The control panel is centered on top of the specified form.</param>
        /// <returns>Returns a value indicating whether opening the control panel was succesful.</returns>
        public bool ShowDisplaySettingsPanel(Form centerForm)
        {
            return SafeNativeMethods.CenterSystemDialog("control", "desk.cpl", centerForm);
        }

        #endregion

        // ************************ Timer

        #region Public - Timer

        /// <summary>
        /// Gets or sets the player's MediaPosition event timer. If enabled the timer raises MediaPosition events and is started or stopped by the player depending on the media playback state. 
        /// </summary>
        public bool TimerEnabled
        {
            get { return _timerEnabled; }
            set
            {
                if (value)
                {
                    if (!_timerEnabled)
                    {
                        _timerEnabled = true;
                        if (_hasDevice && !_paused && !_hasPositionSlider) _timer.Start();
                    }
                }
                else
                {
                    if (_timerEnabled && !_hasPositionSlider) _timer.Stop();
                    _timerEnabled = false;
                }
            }
        }

        /// <summary>
        /// Gets or sets the player's MediaPosition event timer interval in milliseconds (default: 200).
        /// </summary>
        public int TimerInterval
        {
            get { return _timer.Interval; }
            set
            {
                _timer.Interval = value <= 10 ? 10 : value;
            }
        }

        private void AV_TimerTick(object sender, EventArgs e)
        {
            if (_hasPositionSlider)
            {
                _testPos2 = PositionX;
                if (_testPos2 <= _positionSlider.Minimum) _positionSlider.Value = _positionSlider.Minimum;
                else if (_testPos2 >= _positionSlider.Maximum) _positionSlider.Value = _positionSlider.Maximum;
                else _positionSlider.Value = _testPos2;
            }

            if (_timerEnabled && MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);
        }

        #endregion


        // ******************************** Private Properties and Methods

        // ************************ Play

        #region Private - PlayerStartInfo

        private void AV_GetStartInfo()
        {
            _siDisplay = _display;
            _siStartPosition = TimeSpan.FromMilliseconds(_startPosition);
            _siEndPosition = TimeSpan.FromMilliseconds(_endPosition);
            _siRepeat = _repeat;
        }

        #endregion

        #region Private - Play / PlayMedia

        private int AV_Play()
        {
            if (_busyStarting) return 0;
            if (_siFileName.Length < 4)
            {
                _lastError = Global.MCIERR_FILENAME_REQUIRED;
                return _lastError;
            }
            _busyStarting = true;

            if (_hasDevice) AV_CloseDevice(false, true);

            _fileName = _siFileName;
            if (_siDisplay == null && _display == null)
            {
                _siDisplay = _mciNotify;
                _hasOwnWindow = true;
            }
            else
            {
                _hasOwnWindow = false;
            }

            if (_siDisplay != _display)
            {
                AV_SetDisplay(_siDisplay, false);
                Application.DoEvents();
            }

            _startPosition = (int)_siStartPosition.TotalMilliseconds;
            _endPosition = (int)_siEndPosition.TotalMilliseconds;
            _repeat = _siRepeat;

            int retVal = AV_OpenDevice();
            Application.DoEvents();

            if (_hasOwnWindow) AV_SetDisplay(null, true);

            if (retVal == Global.MCIERR_NO_ERROR)
            {
                if (!_hasOwnWindow && _fullScreen)
                {
                    AV_SetFullScreenMode(_fullScreenMode, true);
                }

                retVal = AV_PlayMedia(true);
                if (retVal == Global.MCIERR_NO_ERROR)
                {
                    if (_overlay != null)
                    {
                        if (!_hasOverlayShown)
                        {
                            AV_SetOverlay(_overlay);
                            Application.DoEvents();
                        }
                        else _display.Invalidate();
                    }

                    if (_hasPositionSlider)
                    {
                        if (_psHandlesProgress)
                        {
                            _positionSlider.Minimum = _startPositionMedia;
                            _positionSlider.Maximum = _endPositionMedia == 0 ? _mediaLength: _endPositionMedia;
                        }
                        else
                        {
                            _positionSlider.Minimum = 0;
                            _positionSlider.Maximum = _mediaLength;
                        }
                    }

                    if (MediaStarted != null) MediaStarted(this, EventArgs.Empty);
                    if (MediaPositionChanged != null)
                    {
                        _timerEnabled = true;
                        MediaPositionChanged(this, EventArgs.Empty);
                    }

                    if (_timerEnabled || _hasPositionSlider)
                    {
                        _timer.Start();
                        if (_hasPositionSlider) _positionSlider.Value = _startPositionMedia;
                    }
                }
            }
            _busyStarting = false;
            _lastError = retVal;
            return retVal;
        }

        private int AV_PlayMedia(bool newPlay)
        {
            if (newPlay)
            {
                _startPositionMedia = _startPosition;
                _endPositionMedia = _endPosition <= _startPositionMedia ? 0 : _endPosition;
                if (MediaStartEndMediaChanged != null) MediaStartEndMediaChanged(this, EventArgs.Empty);

                _startPosition = 0;
                _endPosition = 0;
                if (MediaStartEndChanged != null) MediaStartEndChanged(this, EventArgs.Empty);
            }

            _mciBuffer.Length = 0;
            _mciBuffer.Append("play ").Append(_deviceId);

            if (_startPositionMedia > 0)
            {
                _mciBuffer.Append(" from ").Append(_startPositionMedia);
                if (_endPositionMedia > _startPositionMedia)
                {
                    _mciBuffer.Append(" to ").Append(_endPositionMedia);
                }
            }
            else if (_endPositionMedia > 0)
            {
                _mciBuffer.Append(" from 0 to ").Append(_endPositionMedia);
            }
            _mciBuffer.Append(" notify");

            _resumeIsPlay = false;

            Application.DoEvents();
            int retVal = SafeNativeMethods.mciSendString(_mciBuffer.ToString(), null, 0, _mciNotify.Handle);
            Application.DoEvents();

            if (newPlay)
            {
                if (retVal == Global.MCIERR_NO_ERROR)
                {
                    _playing = true;
                    if (_hasVideo && !_videoEnabled) AV_SetVideoEnabled(_videoEnabled);
                    if (_paused) AV_MciSendString("pause");
                    Application.DoEvents();
                }
                else
                {
                    AV_CloseDevice(false, false);
                }
            }
            _lastError = retVal;
            return retVal;
        }

        #endregion

        #region Private - Device Open / Close

        private int AV_OpenDevice()
        {
            int retVal = Global.MCIERR_NO_ERROR;
            int aliasRepeat = Global.ALIAS_REPEAT;

            _mciVideoEnabled = DEFAULT_VIDEO_ENABLED;
            _mciAudioEnabled = DEFAULT_AUDIO_ENABLED;
            _mciAudioVolume = DEFAULT_AUDIO_VOLUME;
            _mciSpeed = DEFAULT_SPEED;

            do // repeat in (very rare) case the device alias (= deviceID) already exists
            {
                _mciBuffer.Length = 0;
                _mciBuffer.Append("open \"")
                .Append(_fileName)
                .Append("\" type mpegvideo alias ")
                .Append(_deviceId)
                .Append(" parent ")
                .Append(_display.Handle)
                .Append(" style child");

                Application.DoEvents();
                retVal = SafeNativeMethods.mciSendString(_mciBuffer.ToString(), null, 0, IntPtr.Zero);
                Application.DoEvents();

                if (retVal == Global.MCIERR_DUPLICATE_ALIAS) _deviceId = (Global.RandomNumber.Next(10, 100000000)) + "AVPD";
            }
            while (retVal == Global.MCIERR_DUPLICATE_ALIAS && aliasRepeat-- > 0);

            if (retVal == Global.MCIERR_INTERNAL)
            {
                _mciBuffer.Length = 0;
                _mciBuffer.Append("open \"")
                .Append(_fileName)
                .Append("\" alias ")
                .Append(_deviceId)
                .Append(" parent ")
                .Append(_display.Handle)
                .Append(" style child");

                Application.DoEvents();
                retVal = SafeNativeMethods.mciSendString(_mciBuffer.ToString(), null, 0, IntPtr.Zero);
                Application.DoEvents();

                if (retVal != Global.MCIERR_NO_ERROR) retVal = Global.MCIERR_INTERNAL; // if any error, restore the 'old' errorcode
            }

            if (retVal == Global.MCIERR_NO_ERROR)
            {
                _hasDevice = true;
                AV_SetDisplayMode(_displayMode);

                if (_hasOwnWindow && _hasVideo)
                {
                    SafeNativeMethods.mciSendString("close " + _deviceId, null, 0, IntPtr.Zero);

                    _hasDevice = false;

                    _hasAudio = false;
                    _hasVideo = false;
                    _hasMediaLength = false;

                    _hasVideoBounds = false;
                    _hasVideoSourceSize = false;

                    retVal = Global.MCIERR_NO_WINDOW;
                }
                else
                {
                    AV_MciSendString("set", "time format ms");
                    AV_MciSendString("set", "seek exactly on");

                    _mediaLength = Length;

                    AV_SetAudioEnabled(_audioEnabled);
                    AV_SetAudioVolume(_audioVolume);

                    AV_SetSpeed(_speed);

                    if (MediaOpened != null) MediaOpened(this, EventArgs.Empty);
                }
            }

            _lastError = retVal;
            return retVal;
        }

        private void AV_CloseDevice(bool purge, bool stopped)
        {
            if (_hasDevice)
            {
                _timer.Stop();

                SafeNativeMethods.mciSendString("close " + _deviceId, null, 0, IntPtr.Zero);
                Application.DoEvents();

                AV_RemoveDisplay(purge);
                Application.DoEvents();

                _hasDevice = false;
                _playing = false;
                _resumeIsPlay = false;

                _startPositionMedia = 0;
                _endPositionMedia = 0;
                if (MediaStartEndMediaChanged != null) MediaStartEndMediaChanged(this, EventArgs.Empty);

                _hasAudio = false;
                _hasVideo = false;
                _hasMediaLength = false;

                _hasVideoBounds = false;
                _hasVideoSourceSize = false;

                if (_hasPositionSlider) _positionSlider.Value = _positionSlider.Minimum;
                if (stopped)
                {
                    if (MediaStoppedNotice != null) MediaStoppedNotice(this, EventArgs.Empty);
                    if (MediaStopped != null) MediaStopped(this, EventArgs.Empty);
                }
                if (MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);
            }
            _lastError = Global.MCIERR_NO_ERROR;
        }

        #endregion

        #region Private - Speed

        private void AV_SetSpeed(int speed)
        {
            _lastError = Global.MCIERR_NO_ERROR;

            _speed = speed <= 0 ? 0 : speed;
            if (_hasDevice && speed != _mciSpeed)
            {
                _lastError = AV_MciSendString("set", "speed " + speed);
                if (_lastError == Global.MCIERR_NO_ERROR) _mciSpeed = speed;
            }
            if (MediaSpeedChanged != null) MediaSpeedChanged(this, EventArgs.Empty);
        }

        #endregion

        // ************************ Video / Audio

        #region Private - Video / Audio

        private void AV_SetVideoEnabled(bool enabled)
        {
            int retVal = Global.MCIERR_NO_ERROR;

            _videoEnabled = enabled;

            if (_videoEnabled != _mciVideoEnabled)
            {
                if (_hasDevice)
                {
                    if (enabled)
                    {
                        if (_hasDisplay)
                        {
                            retVal = AV_MciSendString("window", "state show");
                            if (_playing && _hasVideo)
                            {
                                AV_MciSendString("pause");
                                AV_MciSendString("resume");
                                if (_paused) AV_MciSendString("pause");
                            }
                        }
                    }
                    else
                    {
                        retVal = AV_MciSendString("window", "state hide");
                    }
                    if (retVal == Global.MCIERR_NO_ERROR)
                    {
                        _mciVideoEnabled = enabled;
                        if (MediaVideoEnabledChanged != null) MediaVideoEnabledChanged(this, EventArgs.Empty);
                    }
                }
            }
            _lastError = retVal;
        }

        private void AV_SetAudioEnabled(bool enabled)
        {
            int retVal = Global.MCIERR_NO_ERROR;

            _audioEnabled = enabled;
            if (_audioEnabled != _mciAudioEnabled)
            {
                if (_hasDevice)
                {
                    retVal = AV_MciSendString("setaudio", enabled ? "on" : "off");
                    if (retVal == Global.MCIERR_NO_ERROR)
                    {
                        _mciAudioEnabled = enabled;
                        if (MediaAudioEnabledChanged != null) MediaAudioEnabledChanged(this, EventArgs.Empty);
                    }
                    else
                    {
                        _audioEnabled = !_audioEnabled;
                    }
                }
            }
            _lastError = retVal;
        }

        private void AV_SetAudioVolume(int volume)
        {
            if (volume < 2) volume = 1; // volume 0 gives error (?) x64 only (?)
            else if (volume > 1000) volume = 1000;
            _audioVolume = volume;

            if (volume == _mciAudioVolume && _hasAudio)
            {
                _lastError = Global.MCIERR_NO_ERROR;
                return;
            }
            AV_SetAudioBalance(_audioBalance, true);
        }

        private void AV_SetAudioBalance(int balance, bool volumeOnly)
        {
            int retVal = Global.MCIERR_NO_ERROR;
            int leftVolume, rightVolume;

            if (balance < 1) balance = 0;
            else if (balance > 1000) balance = 1000;
            _audioBalance = balance;

            if (balance <= 500)
            {
                leftVolume = _audioVolume;
                rightVolume = (int)((balance / 500f) * _audioVolume);
            }
            else
            {
                leftVolume = (int)(((1000 - balance) / 500f) * _audioVolume);
                rightVolume = _audioVolume;
            }

            if (_hasDevice)
            {
                retVal = AV_MciSendString("setaudio", "left volume to " + leftVolume.ToString());
                if (retVal == Global.MCIERR_NO_ERROR)
                {
                    _hasAudio = true;
                    retVal = AV_MciSendString("setaudio", "right volume to " + rightVolume.ToString());
                    _mciAudioVolume = _audioVolume;
                }
                else
                {
                    _hasAudio = false;
                }
            }

            if (volumeOnly)
            {
                if (MediaAudioVolumeChanged != null) MediaAudioVolumeChanged(this, EventArgs.Empty);
            }
            else
            {
                if (MediaAudioBalanceChanged != null) MediaAudioBalanceChanged(this, EventArgs.Empty);
            }
            _lastError = retVal;
        }

        #endregion

        // ************************ Display

        #region Private - Display

        private int AV_SetDisplay(Control display, bool setAll)
        {
            bool changeDisplay = false;
            bool oldFullScreen = false;

            int retVal = Global.MCIERR_NO_ERROR;

            if (display != _display)
            {
                if ((display != null) && _hasDevice)
                {
                    if (_hasVideo)
                    {
                        _siFileName = _fileName;
                        _siDisplay = display;
                        int oldStartPosition = _startPositionMedia;
                        _siEndPosition = TimeSpan.FromMilliseconds(_endPositionMedia);
                        _siRepeat = _repeat;
                        _siStartPosition = Position;

                        retVal = AV_Play();
                        _startPositionMedia = oldStartPosition;
                    }
                }
                else
                {
                    if (_hasDisplay)
                    {
                        changeDisplay = true;
                        oldFullScreen = _fullScreen;
                        Form oldOverlay = _overlay;
                        bool oldHasOverlay = _hasOverlay;

                        AV_RemoveDisplay(true);
                        _overlay = oldOverlay;
                        _hasOverlay = oldHasOverlay;
                    }
                    if (display != null)
                    {
                        _display = display;
                        _hasDisplay = true;

                        AV_SetDisplayEvents();
                        Application.DoEvents();
                        if (setAll)
                        {
                            AV_SetFullScreen(changeDisplay ? oldFullScreen : _fullScreen);
                            AV_SetOverlay(_overlay);
                        }
                    }
                }

                if (MediaDisplayChanged != null) MediaDisplayChanged(this, EventArgs.Empty);
            }
            _lastError = retVal;
            return retVal;
        }

        private void AV_RemoveDisplay(bool purge)
        {
            if (_hasDisplay)
            {
                if (_hasOverlay)
                {
                    if (purge)
                    {
                        AV_RemoveOverlay(true);
                    }
                    else
                    {
                        if (!_overlayHold) AV_RemoveOverlay(false);
                    }
                }
                if (purge)
                {
                    if (_fullScreen) AV_ResetFullScreen();
                    if (_hasDisplayEvents) AV_RemoveDisplayEvents();
                    _display = null;
                    _hasDisplay = false;
                }
            }
        }

        private void AV_SetDisplayMode(DisplayMode displayMode)
        {
            int videoTop = 0;
            int videoLeft = 0;
            int videoWidth = 0;
            int videoHeight = 0;

            int retVal = AV_GetDisplayModeSize(displayMode, ref videoTop, ref videoLeft, ref videoWidth, ref videoHeight);
            Application.DoEvents();
            if (retVal == Global.MCIERR_NO_ERROR)
            {
                _hasVideo = true;
                if (_display != null) _display.Invalidate();
            }
            else
            {
                _hasVideo = false;
            }
            _lastError = retVal;
        }

        private int AV_GetDisplayModeSize(DisplayMode mode, ref int top, ref int left, ref int width, ref int height)
        {
            if (_hasDevice)
            {
                if (_hasVideoSourceSize)
                {
                    left = 0;
                    top = 0;
                    width = _videoSourceSize.Width;
                    height = _videoSourceSize.Height;
                }
                else
                {
                    _mciSmallBuffer1.Length = 0;
                    int retVal = SafeNativeMethods.mciSendString("where " + _deviceId + " source", _mciSmallBuffer1, Global.SMALL_BUFFER_SIZE, IntPtr.Zero);
                    if ((retVal != Global.MCIERR_NO_ERROR) || (_mciSmallBuffer1.Length == 0))
                    {
                        top = 0;
                        left = 0;
                        width = 0;
                        height = 0;
                        _lastError = retVal;
                        return retVal;
                    }

                    string[] aSize = _mciSmallBuffer1.ToString().Split(' ');
                    int i;

                    left = 0;
                    for (i = 0; i < aSize[0].Length; ++i)
                    {
                        left = 10 * left + (aSize[0][i] - 48);
                    }
                    
                    top = 0;
                    for (i = 0; i < aSize[1].Length; ++i)
                    {
                        top = 10 * top + (aSize[1][i] - 48);
                    }

                    width = 0;
                    for (i = 0; i < aSize[2].Length; ++i)
                    {
                        width = 10 * width + (aSize[2][i] - 48);
                    }

                    height = 0;
                    for (i = 0; i < aSize[3].Length; ++i)
                    {
                        height = 10 * height + (aSize[3][i] - 48);
                    }

                    width -= left;
                    left = 0;
                    height -= top;
                    top = 0;

                    _videoSourceSize.Height = height;
                    _videoSourceSize.Width = width;
                    _hasVideoSourceSize = true;
                }
            }
            else if (_hasDisplay)
            {
                width = _display.DisplayRectangle.Width;
                left = 0;
                height = _display.DisplayRectangle.Height;
                top = 0;
            }

            if (_hasDisplay)
            {
                double difX;
                double difY;
                switch (mode)
                {
                    case DisplayMode.Manual: // manual
                        left = _videoBounds.Left;
                        top = _videoBounds.Top;
                        height = _videoBounds.Height;
                        width = _videoBounds.Width;
                        break;

                    case DisplayMode.Center: // center
                        left = (_display.DisplayRectangle.Width - width) / 2;
                        top = (_display.DisplayRectangle.Height - height) / 2;
                        break;

                    case DisplayMode.Stretch: // stretch
                        width = _display.DisplayRectangle.Width;
                        height = _display.DisplayRectangle.Height;
                        break;

                    case DisplayMode.Zoom:  // zoom
                        difX = (double)_display.DisplayRectangle.Width / width;
                        difY = (double)_display.DisplayRectangle.Height / height;
                        if (difX < difY)
                        {
                            height = (int)(height * difX);
                            width = (int)(width * difX);
                        }
                        else
                        {
                            height = (int)(height * difY);
                            width = (int)(width * difY);
                        }
                        break;

                    case DisplayMode.ZoomAndCenter: // zoom and center
                        difX = (double)_display.DisplayRectangle.Width / width;
                        difY = (double)_display.DisplayRectangle.Height / height;
                        if (difX < difY)
                        {
                            height = (int)(height * difX);
                            width = (int)(width * difX);
                            top = (_display.DisplayRectangle.Height - height) / 2;
                        }
                        else
                        {
                            height = (int)(height * difY);
                            width = (int)(width * difY);
                            left = (_display.DisplayRectangle.Width - width) / 2;
                        }
                        break;

                    case DisplayMode.SizeToFit: // size to fit
                        if ((width - _display.DisplayRectangle.Width > 0) || (height - _display.DisplayRectangle.Height > 0))
                        {
                            difX = (double)_display.DisplayRectangle.Width / width;
                            difY = (double)_display.DisplayRectangle.Height / height;
                            if (difX < difY)
                            {
                                height = (int)(height * difX);
                                width = (int)(width * difX);
                            }
                            else
                            {
                                height = (int)(height * difY);
                                width = (int)(width * difY);
                            }
                        }
                        break;

                    case DisplayMode.SizeToFitCenter: // size to fit and center
                        if ((width - _display.DisplayRectangle.Width > 0) || (height - _display.DisplayRectangle.Height > 0))
                        {
                            difX = (double)_display.DisplayRectangle.Width / width;
                            difY = (double)_display.DisplayRectangle.Height / height;
                            if (difX < difY)
                            {
                                height = (int)(height * difX);
                                width = (int)(width * difX);
                                top = (_display.DisplayRectangle.Height - height) / 2;
                            }
                            else
                            {
                                height = (int)(height * difY);
                                width = (int)(width * difY);
                                left = (_display.DisplayRectangle.Width - width) / 2;
                            }
                        }
                        else
                        {
                            left = (_display.DisplayRectangle.Width - width) / 2;
                            top = (_display.DisplayRectangle.Height - height) / 2;
                        }
                        break;
                }
                if (mode != DisplayMode.Manual) _videoBounds = new Rectangle(left, top, width, height);
                _hasVideoBounds = true;
            }
            else
            {
                _hasVideoBounds = false;
            }
            _lastError = Global.MCIERR_NO_ERROR;
            return Global.MCIERR_NO_ERROR;
        }

        #endregion

        #region Private - Display Overlay

        private void AV_SetOverlay(Form theOverlay)
        {
            if (theOverlay == null)
            {
                if (_hasOverlay)
                {
                    AV_RemoveOverlay(true);
                    if (MediaOverlayChanged != null) MediaOverlayChanged(this, EventArgs.Empty);
                }
                _lastError = Global.MCIERR_NO_ERROR;
                return;
            }

            if (_display == null)
            {
                _lastError = Global.MCIERR_NO_WINDOW;
                return;
            }

            if (theOverlay.GetType() == _display.FindForm().GetType())
            {
                _lastError = Global.MCIERR_CREATEWINDOW;
                return;
            }
            if (theOverlay == _overlay)
            {
                if (_hasDevice || _overlayHold)
                {
                    if (!_hasOverlayShown) AV_ShowOverlay();
                    else if (!_hasVideo && _overlayMode == OverlayMode.Video && (_overlay.Size != _display.DisplayRectangle.Size))
                    {
                        _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);
                        _overlay.Size = _display.DisplayRectangle.Size;
                    }
                }
                _lastError = Global.MCIERR_NO_ERROR;
                return;
            }

            if (_hasOverlay) AV_RemoveOverlay(true);

            Form myOverlay = theOverlay;
            myOverlay.Owner = _display.FindForm();
            myOverlay.SendToBack();

            myOverlay.ControlBox = false;
            myOverlay.FormBorderStyle = FormBorderStyle.None;
            myOverlay.MaximizeBox = false;
            myOverlay.MinimizeBox = false;
            myOverlay.ShowIcon = false;
            myOverlay.HelpButton = false;
            myOverlay.ShowInTaskbar = false;
            myOverlay.SizeGripStyle = SizeGripStyle.Hide;
            myOverlay.StartPosition = FormStartPosition.Manual;

            myOverlay.MinimumSize = new Size(0, 0);
            myOverlay.MaximumSize = myOverlay.MinimumSize;
            myOverlay.AutoSize = false;
            myOverlay.IsMdiContainer = false;
            myOverlay.TopMost = false;
            myOverlay.UseWaitCursor = false;
            myOverlay.WindowState = FormWindowState.Normal;

            if (myOverlay.TransparencyKey.IsEmpty) myOverlay.TransparencyKey = myOverlay.BackColor;
            if (myOverlay.ContextMenuStrip == null)
            {
                if (_display.ContextMenuStrip != null)
                {
                    myOverlay.ContextMenuStrip = _display.ContextMenuStrip;
                    _hasOverlayMenu = true;
                }
            }
            _overlay = myOverlay;
            _hasOverlay = true;

            if (_hasDevice || _overlayHold) AV_ShowOverlay();

            if (MediaOverlayChanged != null) MediaOverlayChanged(this, EventArgs.Empty);

            _lastError = Global.MCIERR_NO_ERROR;
        }

        private void AV_ShowOverlay()
        {
            if (_hasOverlay)
            {
                bool oldFocus = (Form.ActiveForm == _overlay.Owner || Form.ActiveForm == _overlay); //.ContainsFocus || av_Overlay.Owner.Focused;
                double myOpacity = _overlay.Opacity;

                _overlay.Opacity = 0;
                _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);

                _hasOverlayShown = true;
                AV_SetOverlayEvents();

                _display.Invalidate();
                Application.DoEvents();

                if (_display.Visible && _overlay.Owner.WindowState != FormWindowState.Minimized)
                {
                    SafeNativeMethods.ShowWindow(_overlay.Handle, 4);
                    if (oldFocus) _overlay.Owner.Activate();
                    Application.DoEvents();
                }
                else if (_minimizeEnabled)
                {
                    _minimizedOpacity = myOpacity;
                    myOpacity = 0;
                }

                _overlay.Opacity = myOpacity;
                Application.DoEvents();
            }
        }

        private void AV_HideOverlay()
        {
            if (_hasOverlay)
            {
                AV_RemoveOverlayEvents();
                _overlay.Hide();
                _hasOverlayShown = false;
            }
        }

        private void AV_RemoveOverlay(bool purge)
        {
            if (_hasOverlay)
            {
                _display.FindForm().Focus();
                if (purge)
                {
                    if (_hasOverlayEvents) AV_RemoveOverlayEvents();
                    if (_hasOverlayMenu)
                    {
                        _overlay.ContextMenuStrip = null;
                        _hasOverlayMenu = false;
                    }
                    _overlay.Hide();
                    _overlay.Owner = null;
                    _hasOverlayShown = false;
                    _overlay = null;
                    _hasOverlay = false;
                }
                else
                {
                    AV_HideOverlay();
                }
            }
            _lastError = Global.MCIERR_NO_ERROR;
        }

        private void AV_SetOverlayCanFocus(bool value)
        {
            if (_hasOverlay)
            {
                if (value)
                {
                    AV_RemoveOverlayFocusEvents();
                }
                else
                {
                    AV_SetOverlayFocusEvents();
                    if (_overlay == Form.ActiveForm) _display.FindForm().Activate();
                }
            }
            _overlayCanFocus = value;
            _lastError = Global.MCIERR_NO_ERROR;
        }

        #endregion

        #region Private - FullScreen

        private void AV_SetFullScreen(bool value)
        {
            int retVal = Global.MCIERR_NO_ERROR;

            if (_display == null)
            {
                retVal = Global.MCIERR_CREATEWINDOW;
            }
            else
            {
                if (value)
                {
                    if (!_fullScreen)
                    {
                        retVal = !AV_AddFullScreen(_display.FindForm()) ? Global.MCIERR_CREATEWINDOW : AV_SetFullScreenMode(_fullScreenMode, true);
                    }
                }
                else
                {
                    if (_fullScreen)
                    {
                        retVal = AV_ResetFullScreen();
                    }
                }
            }
            _lastError = retVal;
        }

        private static bool AV_AddFullScreen(Form theForm)
        {
            bool retVal = false;

            if ((_fullScreenForm1 == theForm) || (_fullScreenForm2 == theForm) || (_fullScreenForm3 == theForm) || (_fullScreenForm4 == theForm)) return false;

            if (_fullScreenForm1 == null)
            {
                _fullScreenForm1 = theForm;
                retVal = true;
            }
            else if (_fullScreenForm2 == null)
            {
                _fullScreenForm2 = theForm;
                retVal = true;
            }
            else if (_fullScreenForm3 == null)
            {
                _fullScreenForm3 = theForm;
                retVal = true;
            }
            else if (_fullScreenForm4 == null)
            {
                _fullScreenForm4 = theForm;
                retVal = true;
            }
            return retVal;
        }

        private static void AV_RemoveFullScreen(Form theForm)
        {
            if (_fullScreenForm1 == theForm)
            {
                _fullScreenForm1 = null;
            }
            else if (_fullScreenForm2 == theForm)
            {
                _fullScreenForm2 = null;
            }
            else if (_fullScreenForm3 == theForm)
            {
                _fullScreenForm3 = null;
            }
            else if (_fullScreenForm4 == theForm)
            {
                _fullScreenForm4 = null;
            }
        }

        private int AV_ResetFullScreen()
        {
            FullScreenMode saveMode = _fullScreenMode;

            if (_fullScreen)
            {
                if (FullScreenMode != FullScreenMode.Form) AV_SetFullScreenMode(FullScreenMode.Form, false);

                Form theForm = (Form)_display.TopLevelControl;
                theForm.FormBorderStyle = _fsFormBorder;
                theForm.Bounds = _fsFormBounds;
                _fullScreenMode = saveMode;
                _fullScreen = false;

                AV_RemoveFullScreen(theForm);

                if (MediaFullScreenChanged != null) MediaFullScreenChanged(this, EventArgs.Empty);
            }
            return Global.MCIERR_NO_ERROR;
        }

        private int AV_SetFullScreenMode(FullScreenMode mode, bool doEvent)
        {
            Form theForm = null;
            bool fChanged = false;

            if (!_fullScreen)
            {
                if (_display.GetType().BaseType == typeof(Form))
                    theForm = (Form)_display;
                else
                    theForm = (Form)_display.TopLevelControl;

                _fsFormBounds = theForm.WindowState == FormWindowState.Maximized ? theForm.RestoreBounds : theForm.Bounds;
                _fsFormBorder = theForm.FormBorderStyle;

                Rectangle r = Screen.GetBounds(_display);

                theForm.FormBorderStyle = FormBorderStyle.None;

                SafeNativeMethods.SetWindowPos(theForm.Handle, IntPtr.Zero, r.Left, r.Top, r.Width, r.Height, SafeNativeMethods.SWP_SHOWWINDOW);

                _fullScreenMode = FullScreenMode.Form;
                _fullScreen = true;

                if (theForm == _display || mode == FullScreenMode.Form)
                {
                    if (MediaFullScreenChanged != null) MediaFullScreenChanged(this, EventArgs.Empty);
                    return Global.MCIERR_NO_ERROR;
                }
                fChanged = true;
            }

            if (mode != _fullScreenMode)
            {
                switch (mode)
                {
                    case FullScreenMode.Display:
                        if (_fullScreenMode == FullScreenMode.Form) //  else its mode parent
                        {
                            if (_display.Parent.GetType().BaseType != typeof(Form)) // upsize parent if any
                            {
                                if (_display.Parent.Parent.GetType().BaseType == typeof(Form))
                                {
                                    FS_SetDisplayParent(); // upsize parent
                                }
                                else
                                {
                                    theForm.ResumeLayout();
                                    return Global.MCIERR_CREATEWINDOW; // nested to deep
                                }
                            }
                        }
                        FS_SetDisplay(); // upsize display
                        _fullScreenMode = FullScreenMode.Display;
                        break;

                    case FullScreenMode.Parent:
                        if (_fullScreenMode == FullScreenMode.Display)
                        {
                            FS_ResetDisplay(); // downsize display
                            _fullScreenMode = _display.Parent.GetType().BaseType == typeof(Form) ? FullScreenMode.Form : FullScreenMode.Parent;
                        }
                        else // its mode form
                        {
                            if (_display.Parent.GetType().BaseType != typeof(Form)) // upsize parent if any
                            {
                                if (_display.Parent.Parent.GetType().BaseType == typeof(Form))
                                {
                                    FS_SetDisplayParent(); // upsize parent
                                }
                                else
                                {
                                    theForm.ResumeLayout();
                                    return Global.MCIERR_CREATEWINDOW; // nested to deep
                                }
                                _fullScreenMode = FullScreenMode.Parent;
                            }
                            else
                            {
                                _fullScreenMode = FullScreenMode.Form;
                            }
                        }
                        break;

                    case FullScreenMode.Form:
                        if (_fullScreenMode == FullScreenMode.Parent) // downsize parent
                        {
                            FS_ResetDisplayParent();
                        }
                        else // downsize display
                        {
                            FS_ResetDisplay();
                            if (_display.Parent.GetType().BaseType != typeof(Form)) FS_ResetDisplayParent();
                        }
                        _fullScreenMode = FullScreenMode.Form;
                        break;
                }

                if (doEvent && MediaFullScreenModeChanged != null) MediaFullScreenModeChanged(this, EventArgs.Empty);
            }

            if (fChanged && MediaFullScreenChanged != null) MediaFullScreenChanged(this, EventArgs.Empty);

            if (theForm != null)
            {
                theForm.ResumeLayout();
                Application.DoEvents();
            }

            return Global.MCIERR_NO_ERROR;
        }

        private void FS_SetDisplay()
        {
            _fsDisplayIndex = _display.Parent.Controls.GetChildIndex(_display);
            _fsDisplayBounds = _display.Bounds;
            try
            {
                _fsDisplayBorder = ((Panel)_display).BorderStyle;
                ((Panel)_display).BorderStyle = BorderStyle.None;
            }
            catch { /* ignored */ }

            Rectangle r = _display.Parent.Bounds;
            r.X = r.Y = 0;
            _display.Bounds = r;
            _display.BringToFront();
        }

        private void FS_ResetDisplay()
        {
            try
            {
                ((Panel)_display).BorderStyle = _fsDisplayBorder;
            }
            catch { /* ignored */ }
            _display.Bounds = _fsDisplayBounds;
            _display.Parent.Controls.SetChildIndex(_display, _fsDisplayIndex);
        }

        private void FS_SetDisplayParent()
        {
            _fsParentIndex = _display.Parent.Parent.Controls.GetChildIndex(_display.Parent);
            _fsParentBounds = _display.Parent.Bounds;
            try
            {
                _fsParentBorder = ((Panel)_display.Parent).BorderStyle;
                ((Panel)_display.Parent).BorderStyle = BorderStyle.None;
            }
            catch { /* ignored */ }

            Rectangle r = _display.Parent.Parent.Bounds;
            r.X = r.Y = 0;
            _display.Parent.Bounds = r;
            _display.Parent.BringToFront();
        }

        private void FS_ResetDisplayParent()
        {
            try
            {
                ((Panel)_display.Parent).BorderStyle = _fsParentBorder;
            }
            catch { /* ignored */ }
            _display.Parent.Bounds = _fsParentBounds;
            _display.Parent.Parent.Controls.SetChildIndex(_display.Parent, _fsParentIndex);
        }

        #endregion

        // ************************ MCI

        #region Private - MciSendString

        private int AV_MciSendString(string mciString1, string mciString2 = null)
        {
            _mciBuffer.Length = 0;
            _mciBuffer.Append(mciString1).Append(" ").Append(_deviceId);
            if (mciString2 != null)
            {
                _mciBuffer.Append(" ").Append(mciString2);
            }
            _lastError = SafeNativeMethods.mciSendString(_mciBuffer.ToString(), null, 0, IntPtr.Zero);
            Application.DoEvents();

            return _lastError;
        }

        #endregion

        // ************************ Events

        #region Private - Player Events

        private void AV_SetDisplayEvents()
        {
            if (!_hasDisplayEvents)
            {
                try
                {
                    _display.Paint += AV_DoPaint;
                    _display.Layout += AV_DoLayout;
                    _hasDisplayEvents = true;
                }
                catch { /* ignored */ }
            }
        }

        private void AV_RemoveDisplayEvents()
        {
            if (_hasDisplayEvents)
            {
                try
                {
                    _display.Paint -= AV_DoPaint;
                    _display.Layout -= AV_DoLayout;
                }
                catch { /* ignored */ }
                _hasDisplayEvents = false;
                AV_RemoveOverlayEvents();
            }
        }

        private void AV_SetOverlayEvents()
        {
            if (!_hasOverlayEvents)
            {
                try
                {
                    _display.FindForm().Move += AV_DoMove;
                    _overlay.FormClosing += AV_Overlay_FormClosing;
                    _hasOverlayEvents = true;
                    if (!_overlayCanFocus) AV_SetOverlayFocusEvents();
                }
                catch { /* ignored */ }
                AV_MinimizeActivate(true);
            }
        }

        private void AV_RemoveOverlayEvents()
        {
            if (_hasOverlayEvents)
            {
                try
                {
                    _display.FindForm().Move -= AV_DoMove;
                    _overlay.FormClosing -= AV_Overlay_FormClosing;
                    AV_RemoveOverlayFocusEvents();
                }
                catch { /* ignored */ }
                _hasOverlayEvents = false;
                AV_MinimizeActivate(false);
            }
        }

        // Prevent overlay being closed by user (with OverlayCanFocus)
        private static void AV_Overlay_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing) e.Cancel = true;
        }

        private void AV_SetOverlayFocusEvents()
        {
            if (!_hasOverlayFocusEvents)
            {
                try
                {
                    _overlay.Activated += AV_DoActivated;
                    _hasOverlayFocusEvents = true;
                }
                catch { /* ignored */ }
            }
        }

        private void AV_RemoveOverlayFocusEvents()
        {
            if (_hasOverlayFocusEvents)
            {
                try
                {
                    _overlay.Activated -= AV_DoActivated;
                }
                catch { /* ignored */ }
                _hasOverlayFocusEvents = false;
            }
        }

        private void AV_DoLayout(object sender, LayoutEventArgs e)
        {
            _display.Invalidate();
            if (_hasVideo && (_displayMode == DisplayMode.Normal || _displayMode == DisplayMode.Center || _displayMode == DisplayMode.Manual))
            {
                //if (_resizeFormRefresh) _display.FindForm().Refresh(); // changed 0.46
                if (_resizeFormRefresh) _display.FindForm().Invalidate(true);
                else if (_paused) _display.Refresh();
            }
        }

        private void AV_DoPaint(object sender, PaintEventArgs e)
        {
            if ((_display.Width < 4) || (_display.Height < 4)) return;

            int videoTop = 0;
            int videoLeft = 0;
            int videoWidth = 0;
            int videoHeight = 0;

            AV_GetDisplayModeSize(_displayMode, ref videoTop, ref videoLeft, ref videoWidth, ref videoHeight);

            if (_hasOverlayEvents && _hasOverlayShown)
            {
                // Set Overlay location and size
                if ((_overlayMode == OverlayMode.Video) && _hasVideo && _hasVideoBounds)
                {
                    _intersect = Rectangle.Intersect(_display.DisplayRectangle, _videoBounds);
                    if (_intersect == Rectangle.Empty)
                    {
                        _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);
                        _overlay.Size = _display.DisplayRectangle.Size;
                    }
                    else
                    {
                        _overlay.Location = _display.PointToScreen(_intersect.Location);
                        _overlay.Size = _intersect.Size;
                    }
                }
                else
                {
                    _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);
                    _overlay.Size = _display.DisplayRectangle.Size;
                }
            }

            _paintCommand.Length = 0;
            _paintCommand.Append("put ")
                .Append(_deviceId)
                .Append(" window at ")
                .Append(videoLeft.ToString())
                .Append(" ")
                .Append(videoTop.ToString())
                .Append(" ")
                .Append(videoWidth.ToString())
                .Append(" ")
                .Append(videoHeight.ToString());
            SafeNativeMethods.mciSendString(_paintCommand.ToString(), null, 0, IntPtr.Zero);
        }

        private void AV_DoMove(object sender, EventArgs e)
        {
            if ((_overlayMode == OverlayMode.Video) && _hasVideoBounds)
            {
                if (_videoBounds.Left < 0)
                {
                    if (_videoBounds.Top < 0) _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);
                    else
                    {
                        _movePoint.X = _display.DisplayRectangle.Location.X;
                        _movePoint.Y = _videoBounds.Y;
                        _overlay.Location = _display.PointToScreen(_movePoint);
                    }
                }
                else if (_videoBounds.Top < 0)
                {
                    _movePoint.X = _videoBounds.X;
                    _movePoint.Y = _display.DisplayRectangle.Location.Y;
                    _overlay.Location = _display.PointToScreen(_movePoint);
                }
                else _overlay.Location = _display.PointToScreen(_videoBounds.Location);
            }
            else
            {
                _overlay.Location = _display.PointToScreen(_display.DisplayRectangle.Location);
            }
        }

        private void AV_DoActivated(object sender, EventArgs e)
        {
            if (_hasOverlayEvents)
            {
                Application.DoEvents();
                _overlay.Owner.Activate();
            }
        }

        private void AV_EndOfMedia(bool seek)
        {
            if (_repeat)
            {
                if (!_busyStarting)
                {
                    _busyStarting = true;

                    if (MediaRepeating != null) MediaRepeating(this, EventArgs.Empty);

                    // fix problem with 'stuck repeating mp4 movies' ...:
                    if (_hasVideo && _startPositionMedia == 0) _startPositionMedia = 1;

                    if (seek) AV_MciSendString("seek", "to " + _startPositionMedia);
                    AV_PlayMedia(false);

                    // ... and set it back:
                    if (_startPositionMedia == 1) _startPositionMedia = 0;

                    Application.DoEvents();

                    _busyStarting = false;
                    if (MediaRepeated != null) MediaRepeated(this, EventArgs.Empty);
                }
            }
            else
            {
                AV_CloseDevice(false, false);
                if (MediaEndedNotice != null) MediaEndedNotice(this, EventArgs.Empty);
                if (MediaEnded != null) MediaEnded(this, EventArgs.Empty);
            }
        }

        #endregion


        // ******************************** Slider (TrackBar) Managers

        #region Public - PositionSlider Manager

        #region PositionSlider Fields

        private TrackBar    _positionSlider;
        private bool        _hasPositionSlider;

        private bool        _psHorizontal;
        private bool        _psLiveSeek = true;
        private bool        _psTracking;

        private bool        _psHandlesProgress;
        private int         _psValue;
        private bool        _psBusy;
        private bool        _psSkipped;

        private Point       _psOverlayMouse;

        #endregion

        /// <summary>
        /// Gets or sets the media position slider (trackbar) that is controlled by the player.
        /// </summary>
        public TrackBar PositionSlider
        {
            get { return _positionSlider; }
            set
            {
                if (value != _positionSlider)
                {
                    if (_hasPositionSlider)
                    {
                        if (!_timerEnabled) _timer.Stop();

                        _positionSlider.MouseDown -= PositionSlider_MouseDown;
                        _positionSlider.MouseUp -= PositionSlider_MouseUp;
                        _positionSlider.MouseMove -= PositionSlider_MouseMove;
                        _positionSlider.Scroll -= PositionSlider_Scroll;

                        _positionSlider = null;
                        _hasPositionSlider = false;

                        _psTracking = false;
                        _psValue = 0;
                        _psBusy = false;
                        _psSkipped = false;
                    }

                    if (value != null)
                    {
                        _positionSlider = value;
                        _hasPositionSlider = true;

                        _psHorizontal = (_positionSlider.Orientation == Orientation.Horizontal);
                        _positionSlider.SmallChange = 0;
                        _positionSlider.LargeChange = 0;
                        _positionSlider.TickFrequency = 0;

                        SetPositionSliderMode(_psHandlesProgress);

                        // add events
                        _positionSlider.MouseDown += PositionSlider_MouseDown;
                        _positionSlider.MouseUp += PositionSlider_MouseUp;
                        _positionSlider.MouseMove += PositionSlider_MouseMove;
                        _positionSlider.Scroll += PositionSlider_Scroll;

                        if (_playing && !_timerEnabled) _timer.Start();
                    }
                    _lastError = Global.MCIERR_NO_ERROR;
                }
            }
        }

        /// <summary>
        /// Gets or sets the mode (track or progress) of the media position slider (trackbar) that is controlled by the player.
        /// </summary>
        public PositionSliderMode PositionSliderMode
        {
            get { return _psHandlesProgress ? PositionSliderMode.Progress : PositionSliderMode.Track; }
            set { SetPositionSliderMode(value != PositionSliderMode.Track); }
        }

        /// <summary>
        /// Gets or set a value indicating whether immediate playback position updates are enabled when the media position slider (trackbar) is dragged.
        /// </summary>
        public bool PositionSliderLiveUpdate
        {
            get { return _psLiveSeek; }
            set
            {
                _psLiveSeek = value;
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }

        private void SetPositionSliderMode(bool progressMode)
        {
            _psHandlesProgress = progressMode;
            if (_hasPositionSlider)
            {
                if (_psHandlesProgress)
                {
                    _positionSlider.Minimum = _startPositionMedia;
                    _positionSlider.Maximum = _endPositionMedia == 0 ? _mediaLength : _endPositionMedia;

                    if (_playing)
                    {
                        _testPos1 = PositionX;
                        if (_testPos1 < _positionSlider.Minimum) _positionSlider.Value = _positionSlider.Minimum;
                        else if (_testPos1 > _positionSlider.Maximum) _positionSlider.Value = _positionSlider.Maximum;
                        else _positionSlider.Value = _testPos1;
                    }
                }
                else
                {
                    _positionSlider.Minimum = 0;
                    _positionSlider.Maximum = _mediaLength;
                    if (_playing) _positionSlider.Value = PositionX;
                }
            }
        }

        private void PositionSlider_MouseDown(object sender, MouseEventArgs e)
        {
            if (_hasDevice)
            {
                if (e.Button == MouseButtons.Left)
                {
                    _psTracking = true;
                    _psSkipped = false;

                    _timer.Stop();
                    _positionSlider.Scroll -= PositionSlider_Scroll;

                    float pos;
                    if (_psHorizontal)
                    {
                        if (e.X <= 13) pos = 0;
                        else if (e.X >= _positionSlider.Width - 13) pos = 1;
                        else pos = (float)(e.X - 13) / (_positionSlider.Width - 27);
                    }
                    else
                    {
                        if (e.Y <= 13) pos = 1;
                        else if (e.Y >= _positionSlider.Height - 13) pos = 0;
                        else pos = 1 - (float)(e.Y - 13) / (_positionSlider.Height - 27);
                    }

                    if (_psHandlesProgress)
                    {
                        if (Math.Abs(_positionSlider.Value - (int)(pos * (_positionSlider.Maximum - _positionSlider.Minimum)) + _positionSlider.Minimum) > 5) _positionSlider.Value = (int)(pos * (_positionSlider.Maximum - _positionSlider.Minimum)) + _positionSlider.Minimum;
                    }
                    else if (Math.Abs(_positionSlider.Value - (int)(pos * _positionSlider.Maximum)) > 5) _positionSlider.Value = (int)(pos * _positionSlider.Maximum);
                    _psValue = _positionSlider.Value;

                    if (_psLiveSeek) PositionX = _positionSlider.Value;
                }
            }
            else
            {
                _positionSlider.Value = 0;
                _psValue = 0;
            }
        }

        private void PositionSlider_MouseMove(object sender, MouseEventArgs e)
        {
            if (_psTracking)
            {
                if (e.Location == _psOverlayMouse) return; // for overlays with timers
                _psOverlayMouse = e.Location;

                if (_psBusy) _psSkipped = true;
                else
                {
                    if (_positionSlider.Value != _psValue)
                    {
                        _psBusy = true;

                        do
                        {
                            _psSkipped = false;
                            if (_psLiveSeek) PositionX = _positionSlider.Value;
                            else if (MediaPositionChanged != null) MediaPositionChanged(this, EventArgs.Empty);

                        } while (_psSkipped);

                        _psBusy = false;
                    }
                }
            }
        }

        private void PositionSlider_MouseUp(object sender, MouseEventArgs e)
        {
            if (_psTracking)
            {
                if (!_psLiveSeek) PositionX = _positionSlider.Value;

                _positionSlider.Scroll += PositionSlider_Scroll;
                if (!_paused) _timer.Start();

                _psTracking = false;
            }
        }

        private void PositionSlider_Scroll(object sender, EventArgs e)
        {
            if (_hasDevice)
            {
                if (!_psTracking)
                {
                    if (_psBusy) _psSkipped = true;
                    else
                    {
                        _psBusy = true;

                        do
                        {
                            _psSkipped = false;
                            PositionX = _positionSlider.Value;

                        } while (_psSkipped);

                        _psBusy = false;
                    }
                }
            }
            else
            {
                _positionSlider.Value = 0;
                _psValue = 0;
            }
        }

        #endregion

        #region Public - ShuttleSlider Manager

        private TrackBar    _shuttleSlider;

        /// <summary>
        /// Gets or sets the shuttle slider (trackbar for 'Step' method) that is controlled by the player.
        /// </summary>
        public TrackBar ShuttleSlider
        {
            get { return _shuttleSlider; }
            set
            {
                if (value != _shuttleSlider)
                {
                    if (_shuttleSlider != null)
                    {
                        _shuttleSlider.MouseDown -= ShuttleSlider_MouseDown;
                        _shuttleSlider.MouseUp -= ShuttleSlider_MouseUp;

                        _shuttleSlider = null;
                    }

                    if (value != null)
                    {
                        _shuttleSlider = value;

                        _shuttleSlider.SmallChange = 0;
                        _shuttleSlider.LargeChange = 0;

                        _shuttleSlider.TickFrequency = 1;

                        _shuttleSlider.Minimum = -5;
                        _shuttleSlider.Maximum = 5;
                        _shuttleSlider.Value = 0;

                        _shuttleSlider.MouseDown += ShuttleSlider_MouseDown;
                        _shuttleSlider.MouseUp += ShuttleSlider_MouseUp;
                    }
                }
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }

        private void ShuttleSlider_MouseDown(object sender, MouseEventArgs e)
        {
            if (_hasDisplay) _display.Focus();

            if (Playing && Control.MouseButtons == MouseButtons.Left)
            {
                if (_hasVideo) AV_MciSendString("set", "speed 10");
                while (Control.MouseButtons == MouseButtons.Left)
                {
                    if (_shuttleSlider.Value != 0)
                    {
                        if (_shuttleSlider.Value < 0) Step(_shuttleSlider.Value - 1);
                        else Step(_shuttleSlider.Value);
                        System.Threading.Thread.Sleep(25);
                    }
                    Application.DoEvents();
                }
                if (_hasVideo) AV_MciSendString("set", "speed " + _speed);
            }
        }

        private void ShuttleSlider_MouseUp(object sender, MouseEventArgs e)
        {
            _shuttleSlider.Value = 0;
        }

        #endregion

        #region Public - VolumeSlider Manager

        private TrackBar _volumeSlider;

        /// <summary>
        /// Gets or sets the audio volume slider (trackbar) that is controlled by the player.
        /// </summary>
        public TrackBar AudioVolumeSlider
        {
            get { return _volumeSlider; }
            set
            {
                if (value != _volumeSlider)
                {
                    if (_volumeSlider != null)
                    {
                        _volumeSlider.Scroll -= VolumeSlider_Scroll;
                        _volumeSlider = null;
                    }

                    if (value != null)
                    {
                        _volumeSlider = value;

                        _volumeSlider.Minimum = 0;
                        _volumeSlider.Maximum = 1000;
                        _volumeSlider.TickFrequency = 100;
                        _volumeSlider.SmallChange = 100;
                        _volumeSlider.LargeChange = 100;

                        _volumeSlider.Value = AudioVolume;

                        _volumeSlider.Scroll += VolumeSlider_Scroll;
                    }
                }
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }

        private void VolumeSlider_Scroll(object sender, EventArgs e)
        {
            AV_SetAudioVolume(_volumeSlider.Value);
        }

        #endregion

        #region Public - BalanceSlider Manager

        private TrackBar _balanceSlider;

        /// <summary>
        /// Gets or sets the audio balance slider (trackbar) that is controlled by the player.
        /// </summary>
        public TrackBar AudioBalanceSlider
        {
            get { return _balanceSlider; }
            set
            {
                if (value != _balanceSlider)
                {
                    if (_balanceSlider != null)
                    {
                        _balanceSlider.Scroll -= BalanceSlider_Scroll;
                        _balanceSlider = null;
                    }

                    if (value != null)
                    {
                        _balanceSlider = value;

                        _balanceSlider.Minimum = 0;
                        _balanceSlider.Maximum = 1000;
                        _balanceSlider.TickFrequency = 100;
                        _balanceSlider.SmallChange = 100;
                        _balanceSlider.LargeChange = 100;

                        _balanceSlider.Value = AudioBalance;
                        _balanceSlider.Scroll += BalanceSlider_Scroll;
                    }
                }
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }

        private void BalanceSlider_Scroll(object sender, EventArgs e)
        {
            AV_SetAudioBalance(_balanceSlider.Value, false);
        }

        #endregion

        #region Public - SpeedSlider Manager

        private TrackBar    _speedSlider;
        private bool        _speedSliderBusy;

        /// <summary>
        /// Gets or sets the playback speed slider (trackbar) that is controlled by the player.
        /// </summary>
        public TrackBar SpeedSlider
        {
            get { return _speedSlider; }
            set
            {
                if (value != _speedSlider)
                {
                    if (_speedSlider != null)
                    {
                        _speedSlider.Scroll -= SpeedSlider_Scroll;
                        _speedSlider.MouseDown -= SpeedSlider_MouseDown;

                        _speedSlider = null;
                    }

                    if (value != null)
                    {
                        _speedSlider = value;

                        _speedSlider.Minimum = 0;
                        _speedSlider.Maximum = 12;
                        _speedSlider.TickFrequency = 1;
                        _speedSlider.SmallChange = 1;
                        _speedSlider.LargeChange = 1;

                        SpeedSlider_ValueToSlider(Speed);

                        _speedSlider.MouseDown += SpeedSlider_MouseDown;
                        _speedSlider.Scroll += SpeedSlider_Scroll;
                    }
                }
                _lastError = Global.MCIERR_NO_ERROR;
            }
        }

        private void SpeedSlider_MouseDown(object sender, MouseEventArgs e)
        {
            _speedSkipped = false;
        }

        private void SpeedSlider_Scroll(object sender, EventArgs e)
        {
            if (_speedSliderBusy)
            {
                _speedSkipped = true;
            }
            else
            {
                _speedSliderBusy = true;
                do
                {
                    _speedSkipped = false;

                    int scrollSpeed;
                    switch (_speedSlider.Value)
                    {
                        case 0: scrollSpeed = 100;
                            break;
                        case 1: scrollSpeed = 250;
                            break;
                        case 2: scrollSpeed = 333;
                            break;
                        case 3: scrollSpeed = 500;
                            break;
                        case 4: scrollSpeed = 666;
                            break;
                        case 5: scrollSpeed = 750;
                            break;
                        case 6: scrollSpeed = 1000;
                            break;
                        case 7: scrollSpeed = 1500;
                            break;
                        case 8: scrollSpeed = 2000;
                            break;
                        case 9: scrollSpeed = 2500;
                            break;
                        case 10: scrollSpeed = 3000;
                            break;
                        case 11: scrollSpeed = 3500;
                            break;
                        default: scrollSpeed = 4000;
                            break;
                    }
                    AV_SetSpeed(scrollSpeed);
                } while (_speedSkipped);

                _speedSliderBusy = false;
            }
        }

        private void SpeedSlider_ValueToSlider(int speed)
        {
            if (speed < 175) speed = 0;
            else if (speed < 292) speed = 1;
            else if (speed < 416) speed = 2;
            else if (speed < 583) speed = 3;
            else if (speed < 708) speed = 4;
            else if (speed < 875) speed = 5;
            else if (speed < 1250) speed = 6;
            else if (speed < 1750) speed = 7;
            else if (speed < 2250) speed = 8;
            else if (speed < 2750) speed = 9;
            else if (speed < 3250) speed = 10;
            else if (speed < 3750) speed = 11;
            else speed = 12;

            _speedSlider.Value = speed;
        }

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Peter Vegter
United States United States
No Biography provided

You may also be interested in...

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web06 | 2.8.190114.1 | Last Updated 7 Aug 2018
Article Copyright 2010 by Peter Vegter
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid