Click here to Skip to main content
13,828,858 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.91
    August 2018, The Netherlands
    © Copyright 2018 PVS The Netherlands - Free to Use

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

    For use with Microsoft .NET Framework version 2.0 or higher and any CPU.
    Created with Microsoft Visual Studio.

    Articles on CodeProject with information on the use of the PVS.AVPlayer library:
    About the Player: http://www.codeproject.com/Articles/109714/PVS-AVPlayer-MCI-Audio-and-Video-Library
    About the Sound Recorder: http://www.codeproject.com/Articles/1116698/PVS-AVPlayer-MCI-Sound-Recorder

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

    The PVS.AVPlayer library source code is divided into 10 files:

     1. Player.cs            - Player source code
     2. MouseEvents.cs       - extension source code of Player.cs, provides player display mouse events
     3. PeakMeter.cs         - extension source code of Player.cs, provides player audio peak level values
     4. DisplayClones.cs     - extension source code of Player.cs, provides player multiple video displays 
     5. Subtitles.cs         - extension source code of Player.cs, provides player SubRip (.srt) subtitles
     6. Signals.cs           - extension source code of Player.cs, provides player media position signaling
     7. CursorHide.cs        - extension source code of Player.cs, hides the mouse cursor during inactivity
     8. Recorder.cs          - Sound Recorder source code
     9. PlayerRecorder.cs    - code used by both Player.cs (and its extension files) and Recorder.cs
    10. Infolabel.cs         - Info Label (custom ToolTip) source code

    Required references:
    System
    System.Drawing
    System.Windows.Forms

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

    This file: Subtitles.cs

    Player Class
    Extension to file 'Player.cs'.

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

    About Media Control Interface (MCI)
    - for information about MCI, please see https://msdn.microsoft.com/en-us/library/vs/alm/dd757151(v=vs.85).aspx
    - you can find many articles about mci on the internet, search examples: 'c# mci', 'vb mci', 'mcisendstring'
      or the subject of your question.
    - the PVS.AVPlayer library also provides 'direct access' to MCI (e.g. 'Mci.MciSendString').

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

    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 articles with comments and suggestions and, of course, CodeProject:
    thank you Deeksha, Smitha and Sean Ewington for the beautiful articles and all!

    Special thanks to Sean Ewington of CodeProject who also took care of publishing the many code
    updates and changes in the PVS.AVPlayer articles in a friendly, fast, and highly competent manner.
    Thank you very much, Sean!

    Peter Vegter
    August 2018, The Netherlands

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

#region Usings

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

#endregion

namespace PVS.AVPlayer
{
    public partial class Player
    {
        /*
            Subtitles
            Provides SubRip (.srt) subtitles by subscribing to the Player.Events.MediaSubtitleChanged event.
        */

        // ******************************** Subtitles - Fields

        #region Subtitles - Fields

        // constants
        private const int               ST_DEFAULT_DIRECTORY_DEPTH  = 0;

        // static regex - also used with Signals.cs
        internal static Regex           st_TimeRegex = new Regex("(?<start>[0-9:,]+) --> (?<end>[0-9:,]+)", RegexOptions.Compiled);
        internal static Regex           st_TagsRegex = new Regex("<.*?>", RegexOptions.Compiled);

        // classes
        private sealed class SubtitlesSearchData
        {
            internal bool               Found;
            internal string             FileName;
            internal StringBuilder      FilePath = new StringBuilder(260);

            internal SubtitlesSearchData(string fileName)
            {
                FileName = Path.DirectorySeparatorChar + fileName;
            }
        }

        internal sealed class SubtitleItem
        {
            internal double             StartTime;
            internal double             EndTime;
            internal string             Text;

            internal SubtitleItem(double startTime, double endTime, string text)
            {
                StartTime   = startTime;
                EndTime     = endTime;

                // remove last return char (if any)
                if (text.Length > 0 && text[text.Length - 1] == '\r') Text = text.Remove(text.Length - 1);
                else Text = text;
            }
        }

        // event args
        private SubtitleEventArgs       st_SubtitleChangedArgs;

        // general
        internal bool                   st_SubtitlesEnabled;
        internal bool                   st_HasSubtitles;
        internal string                 st_SubtitlesName;   // filename of subtitles being used
        internal SubtitleItem[]         st_SubtitleItems;
        internal int                    st_SubTitleCount;
        private bool                    st_SubtitlesBusy;

        // find and decode subtitles file
        internal string                 st_FileName;        // set by user
        internal string                 st_Directory;       // set by user
        internal int                    st_DirectoryDepth   = ST_DEFAULT_DIRECTORY_DEPTH;
        internal Encoding               st_Encoding         = Encoding.Default;
        internal int                    st_TimeShift;
        internal bool                   st_RemoveHTMLTags   = true;
        internal bool                   st_AudioOnlyEnabled;

        // used with get current subtitle
        internal bool                   st_SubtitleOn;
        internal int                    st_CurrentIndex     = -1;
        private double                  st_CurrentStart;
        private double                  st_CurrentEnd;

        #endregion


        // ******************************** Subtitles - Main

        #region Subtitles - Main

        /// <summary>
        /// Provides access to the subtitles settings of the player (e.g. Player.Subtitles.Enabled).
        /// </summary>
        public Subtitles Subtitles
        {
            get
            {
                if (_subtitlesClass == null) _subtitlesClass = new Subtitles(this);
                return _subtitlesClass;
            }
        }

        #endregion


        // ******************************** Subtitles - Start / Exists / Stop / Eventhandler / Find

        #region Subtitles - Start / Exists / Stop / Position Eventhandler / Find

        internal void Subtitles_Start(bool refresh)
        {
            if (st_HasSubtitles) Subtitles_Stop();

            if (st_SubtitlesEnabled && _playing && (_hasVideo || st_AudioOnlyEnabled))
            {
                string subtitlesFile = Subtitles_Exists();
                if (subtitlesFile != string.Empty)
                {
                    if (Subtitles_GetFromFile(subtitlesFile))
                    {
                        st_CurrentIndex = 0;
                        this.MediaPositionChanged += Subtitles_MediaPositionChanged;

                        st_HasSubtitles = true;
                        st_SubtitlesName = subtitlesFile;

                        if (refresh) OnMediaPositionChanged();
                    }
                }
            }
        }

        // check subtitles file exists (just the file (name), no contents check)
        internal string Subtitles_Exists()
        {
            if (st_HasSubtitles) return st_SubtitlesName;

            string searchFile;
            string initialDirectory;

            if (!string.IsNullOrEmpty(st_FileName)) searchFile = st_FileName;
            else searchFile = this.Media.GetName(MediaName.FileNameWithoutExtension) + Global.SUBTITLES_FILE_EXTENSION;

            if (!string.IsNullOrEmpty(st_Directory)) initialDirectory = st_Directory;
            else initialDirectory = this.Media.GetName(MediaName.DirectoryName);

            return Subtitles_FindFile(searchFile, initialDirectory);
            // TODO check contents of file (Count > 0)
        }

        internal void Subtitles_Stop()
        {
            if (st_HasSubtitles)
            {
                this.MediaPositionChanged -= Subtitles_MediaPositionChanged;

                if (st_SubtitleOn)
                {
                    st_SubtitleChangedArgs._index    = -1;
                    st_SubtitleChangedArgs._subtitle = string.Empty;
                    _mediaSubtitleChanged(this, st_SubtitleChangedArgs);

                    st_SubtitleOn   = false;
                }

                st_HasSubtitles     = false;
                st_SubtitlesName    = string.Empty;

                //st_LastPosition     = 0;
                st_CurrentIndex     = -1;
                st_CurrentStart     = 0;
                st_CurrentEnd       = 0;

                st_FileName         = string.Empty;
                st_TimeShift        = 0;

                Subtitles_Dispose();
            }
        }

        // returns current subtitle string
        private void Subtitles_MediaPositionChanged(object sender, PositionEventArgs e)
        {
            if (!st_SubtitlesBusy)
            {
                st_SubtitlesBusy = true;

                // get player position + synchronize offset
                double position = e.FromBegin.TotalMilliseconds - st_TimeShift;
                if (position < 0) position = 0;

                if (position < st_CurrentStart || position >= st_CurrentEnd)
                {
                    bool backwards = position < st_CurrentStart;

                    int index = 0;
                    if (Subtitle_Find(position, st_CurrentIndex, backwards, out index))
                    {
                        if (!st_SubtitleOn || index != st_CurrentIndex)
                        {
                            st_CurrentIndex = index;

                            st_SubtitleOn = true;
                            st_CurrentStart = st_SubtitleItems[index].StartTime;
                            st_CurrentEnd = st_SubtitleItems[index].EndTime;

                            st_SubtitleChangedArgs._index = index;
                            st_SubtitleChangedArgs._subtitle = st_SubtitleItems[index].Text;
                            _mediaSubtitleChanged(this, st_SubtitleChangedArgs);
                        }
                    }
                    else
                    {
                        if (st_SubtitleOn || index != st_CurrentIndex)
                        {
                            st_CurrentIndex = index;

                            //if (st_CurrentIndex < 1) st_CurrentStart = 0;
                            //else st_CurrentStart = st_SubtitleItems[st_CurrentIndex].EndTime;

                            //if (st_CurrentIndex > st_SubtitleItems.Length - 2) st_CurrentEnd = this.Length;
                            //else st_CurrentEnd = st_SubtitleItems[st_CurrentIndex].StartTime;

                            if (st_CurrentIndex < 1) st_CurrentStart = 0;
                            else st_CurrentStart = st_SubtitleItems[st_CurrentIndex].EndTime;

                            if (st_CurrentIndex > st_SubtitleItems.Length - 1) st_CurrentEnd = this.Length;
                            else st_CurrentEnd = st_SubtitleItems[st_CurrentIndex].StartTime;

                            if (st_SubtitleOn)
                            {
                                st_SubtitleOn = false;
                                st_SubtitleChangedArgs._index = -1;
                                st_SubtitleChangedArgs._subtitle = string.Empty;
                                _mediaSubtitleChanged(this, st_SubtitleChangedArgs);
                            }
                        }
                    }
                }
                st_SubtitlesBusy = false;
            }
        }

        private bool Subtitle_Find(double time, int startIndex, bool backwards, out int index)
        {
            bool found = false;
            index = 0;

            if (st_HasSubtitles)
            {
                if (backwards)
                {
                    for (int i = startIndex; i >= 0; i--)
                    {
                        if (time < st_SubtitleItems[i].EndTime)
                        {
                            if (time >= st_SubtitleItems[i].StartTime)
                            {
                                index = i;
                                found = true;
                                break;
                            }
                        }
                        else
                        {
                            index = i;
                            break;
                        }
                    }
                }
                else
                {
                    int itemCount = st_SubtitleItems.Length;
                    index = itemCount - 1;
                    for (int i = startIndex; i < itemCount; i++)
                    {
                        if (time >= st_SubtitleItems[i].StartTime)
                        {
                            if (time < st_SubtitleItems[i].EndTime)
                            {
                                index = i;
                                found = true;
                                break;
                            }
                        }
                        else
                        {
                            index = i;
                            break;
                        }
                    }
                }
            }
            return found;
        }

        #endregion


        // ******************************** Subtitles - Find File / Search File

        #region Subtitles - Find File / Search File

        private string Subtitles_FindFile(string fileName, string initialDirectory)
        {
            if (string.IsNullOrEmpty(fileName)
                || fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0
                || !Directory.Exists(initialDirectory))
                return string.Empty;

            SubtitlesSearchData data = new SubtitlesSearchData(fileName);
            Subtitles_SearchFile(initialDirectory, st_DirectoryDepth, data);
            return data.Found ? data.FilePath.ToString() : string.Empty;
        }

        private void Subtitles_SearchFile(string directory, int depth, SubtitlesSearchData data)
        {
            try
            {
                data.FilePath.Length = 0;
                data.FilePath.Append(directory).Append(data.FileName);
                data.Found = File.Exists(data.FilePath.ToString());

                if (!data.Found && --depth >= 0)
                {
                    string[] directories = Directory.GetDirectories(directory);
                    for (int i = 0; i < directories.Length; i++)
                    {
                        Subtitles_SearchFile(directories[i], depth, data);
                        if (data.Found) return;
                    }
                }
            }
            catch { /* ignore */ }
        }

        #endregion


        // ******************************** Subtitles - Get From File / Count / Dispose

        #region Subtitles - Get From File / Count / Dispose

        private bool Subtitles_GetFromFile(string fileName)
        {
            bool result = false;

            if (!string.IsNullOrEmpty(fileName))
            {
                StreamReader reader = new StreamReader(fileName, st_Encoding, true);

                int count = Subtitles_Count(reader);
                if (count > 0)
                {
                    bool    error       = false;
                    int     readStep    = 0;
                    Match   m;
                    string  line;

                    TimeSpan startTime  = TimeSpan.Zero;
                    TimeSpan endTime    = TimeSpan.Zero;
                    string   text       = "";

                    int index           = 0;
                    st_SubtitleItems    = new SubtitleItem[count];


                    while ((line = (reader.ReadLine())) != null && !error)
                    {
                        line = line.Trim();
                        if (string.IsNullOrEmpty(line)) continue;

                        int testId;
                        switch (readStep)
                        {
                            case 0: // Id
                                if (int.TryParse(line, out testId))
                                {
                                    readStep = 1;
                                }
                                break;
                            case 1: // Time
                                m = st_TimeRegex.Match(line);
                                if (m.Success)
                                {
                                    if (!TimeSpan.TryParse(m.Groups["start"].Value.Replace(",", "."), out startTime)) error = true;
                                    if (!TimeSpan.TryParse(m.Groups["end"].Value.Replace(",", "."), out endTime)) error = true;
                                }
                                else error = true;
                                readStep = 2;
                                break;
                            case 2: // Text
                                if (int.TryParse(line, out testId)) // if not id (subtitle number)
                                {
                                    if (st_RemoveHTMLTags) st_SubtitleItems[index++] = new SubtitleItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, st_TagsRegex.Replace(text, string.Empty));
                                    else st_SubtitleItems[index++] = new SubtitleItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, text);
                                    text = "";
                                    readStep = 1;
                                }
                                else text += line + '\r';
                                break;
                        }
                    }
                    if (!error)
                    {
                        if (text != "")
                        {
                            if (st_RemoveHTMLTags) st_SubtitleItems[index++] = new SubtitleItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, st_TagsRegex.Replace(text, string.Empty));
                            else st_SubtitleItems[index++] = new SubtitleItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, text);
                        }
                        else
                        {
                            st_SubtitleItems[index++] = new SubtitleItem(0, 0, string.Empty);
                        }
                        st_SubTitleCount = count;
                        result = true;
                    }
                }
                reader.Close();
            }
            return result;
        }

        // ... also for dimensioning the subtitles array
        private int Subtitles_Count(StreamReader reader)
        {
            bool error          = false;
            string line;
            int readStep        = 0;
            int count           = 0;
            TimeSpan startTime  = TimeSpan.Zero;
            TimeSpan endTime    = TimeSpan.Zero;

            while ((line = (reader.ReadLine())) != null && !error)
            {
                line = line.Trim();
                if (string.IsNullOrEmpty(line)) continue;

                int testId;
                switch (readStep)
                {
                    case 0: // Id
                        if (int.TryParse(line, out testId)) readStep = 1;
                        break;
                    case 1: // Time
                        Match m = st_TimeRegex.Match(line);
                        if (m.Success)
                        {
                            if (!TimeSpan.TryParse(m.Groups["start"].Value.Replace(",", "."), out startTime)) error = true;
                            if (!TimeSpan.TryParse(m.Groups["end"].Value.Replace(",", "."), out endTime)) error = true;
                            ++count;
                        }
                        else error = true;
                        readStep = 2;
                        break;
                    case 2: // Text
                        if (int.TryParse(line, out testId))
                        {
                            readStep = 1;
                        }
                        break;
                }
            }
            reader.DiscardBufferedData();
            reader.BaseStream.Seek(0, SeekOrigin.Begin);
            reader.BaseStream.Position = 0;
            return error ? 0 : count;
        }

        private void Subtitles_Dispose()
        {
            if (st_SubtitleItems != null)
            {
                for (int i = 0; i < st_SubtitleItems.Length; i++)
                {
                    st_SubtitleItems[i] = null;
                }

                st_SubtitleItems = null;
                st_SubTitleCount = 0;
            }
        }

        #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
Web04 | 2.8.190114.1 | Last Updated 7 Aug 2018
Article Copyright 2010 by Peter Vegter
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid