Click here to Skip to main content
13,899,794 members
Click here to Skip to main content

Stats

740.8K views
30.1K 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: Signals.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.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

#endregion

namespace PVS.AVPlayer
{
    public partial class Player
    {
        /*
            Media Signals

            Provides media position signals by subscribing to the Player.Events.MediaSignal event.
            Media signal data provided by .msf files and/or Player.Signals.Add method.

            A .msf media signals file has the same format as a .srt subtitles file.
            This has the advantage that you can test the contents of a .msf file by temporarily changing
            the file extension to .srt, using your existing .srt files as a basis for .msf files and
            creating (more extensive) .msf files with .srt software.

            The format of a .msf (.srt) file is as follows (example with 2 signals):

            1
            00:00:10,000 --> 00:00:20,000
            Signal message 1

            2
            00:01:00,000 --> 00:01:10,000
            Signal message 2

            On each first line is the number of the media signal (the numbers do not necessarily have
            to be consecutive), on the second line the start and end time of the media signal
            (separated by -->) and below the media signal message itself (text on one or multiple lines).
            Although the media signal numbers do not have to be consecutive, the times of the different
            media signals have to be. Moreover, the times of the media signals should not overlap.

            Instead of using a file you can also generate media signals via commands, for example:
            myPlayer.Signals.Add(TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20), "Signal message 1")
            If a signal file is already loaded, the Add commands are added to the media signals from the
            file (if no overlaps occur). If the Add command is used when no media is playing (and media
            signals have been created), no .msf file is loaded when the next media starts playing.

            At the end of media playback, all media signals are always removed from the player.

            A media signal is only 'reported' if the playing time of the relevant media 'enters' the indicated
            signal period (also during 'seeking', for example via the position slider (also when seeking
            backwards) - see also Player.Sliders.PositionSkipSignals).

            - the interpretation of the media signals (the media signal messages) should be done by your application,
            - do not place a number at the beginning of message lines without text behind it, this is seen as a media signal number
            - if the processing of a media signal takes longer than approx. 100 ms, use a different application thread.

            To receive media signals, you must subscribe to the Player.Events.MediaSignal event.
        */

        // ******************************** Media Signals - Fields

        #region Media Signals - Fields

        // constants
        private const int           MS_DEFAULT_DIRECTORY_DEPTH  = 0;

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

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

        internal sealed class SignalItem
        {
            internal double         StartTime;
            internal double         EndTime;
            internal string         Message;

            internal                SignalItem(double startTime, double endTime, string message)
            {
                StartTime = startTime;
                EndTime = endTime;

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

        // Player.Events.MediaSignal event args
        private SignalEventArgs     ms_SignalEventArgs;

        // general
        internal bool               ms_SignalsEnabled;
        internal bool               ms_HasSignals;
        internal bool               ms_HasSignalsFile;
        private bool                ms_HasSignalsPending;
        internal string             ms_SignalsName;     // filename of signals being used
        internal List<SignalItem>   ms_SignalItems;
        private bool                ms_SignalsBusy;

        // find and decode media signals file
        internal string             ms_FileName;        // set by user
        internal string             ms_Directory;       // set by user
        internal int                ms_DirectoryDepth   = MS_DEFAULT_DIRECTORY_DEPTH;
        internal Encoding           ms_Encoding         = Encoding.Default;
        internal int                ms_TimeShift;

        // used with get current media signal
        internal int                ms_CurrentIndex     = -1;
        private double              ms_CurrentStart;
        private double              ms_CurrentEnd;

        #endregion


        // ******************************** Media Signals - Main

        #region Media Signals - Main

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

        #endregion


        // ******************************** Media Signals - Add

        #region Media Signals - Add

        internal int MS_Add(double startTime, double endTime, string message)
        {
            // params tested for null/empty at calling method

            // check signal time integrity
            int result = Global.MCIERR_OUTOFRANGE;
            if (startTime > 0 && startTime < endTime)
            {
                if (ms_HasSignals || ms_HasSignalsPending)
                {
                    // check overlap with existing signals / find insert index
                    int index = -2;
                    int itemCount = ms_SignalItems.Count;

                    for (int i = 0; i < itemCount; i++)
                    {
                        if (startTime < ms_SignalItems[i].EndTime)
                        {
                            if (startTime < ms_SignalItems[i].StartTime && endTime <= ms_SignalItems[i].StartTime) index = i;
                            else index = -1;
                            break;
                        }
                    }

                    if (index != -1)
                    {
                        if (index == -2) ms_SignalItems.Add(new SignalItem(startTime, endTime, message));
                        else ms_SignalItems.Insert(index, new SignalItem(startTime, endTime, message));
                        result = Global.MCIERR_NO_ERROR;
                    }
                }
                else
                {
                    ms_SignalItems = new List<SignalItem>(10);
                    ms_SignalItems.Add(new SignalItem(startTime, endTime, message));

                    if (ms_SignalsEnabled && _playing)
                    {
                        ms_CurrentIndex = 0;
                        this.MediaPositionChanged += MS_MediaPositionChanged;

                        ms_HasSignals = true;
                        OnMediaPositionChanged();
                    }
                    else
                    {
                        ms_HasSignalsPending = true;
                    }
                    result = Global.MCIERR_NO_ERROR;
                }
            }
            return result;
        }

        #endregion


        // ******************************** Media Signals - Start / Exists / Stop / Eventhandler / Find

        #region Media Signals - Start / Exists / Stop / Eventhandler / Find

        internal void MS_Start(bool refresh)
        {
            // stop signals
            if (ms_HasSignals) MS_Stop();

            if (ms_SignalsEnabled && _playing)
            {
                if (ms_HasSignalsPending)
                {
                    ms_HasSignals = true;
                    ms_HasSignalsPending = false;
                }
                else //if (ms_SignalsFileEnabled)
                {
                    string signalsFile = MS_Exists();
                    if (signalsFile != string.Empty)
                    {
                        if (MS_GetFromFile(signalsFile))
                        {
                            ms_HasSignals = true;
                            ms_HasSignalsFile = true;
                            ms_SignalsName = signalsFile;
                        }
                    }
                }

                if (ms_HasSignals)
                {
                    ms_CurrentIndex = 0;
                    this.MediaPositionChanged += MS_MediaPositionChanged;
                    if (refresh) OnMediaPositionChanged();
                }
            }
        }

        // check media signals file exists (just the file (name), no contents check)
        internal string MS_Exists()
        {
            if (ms_HasSignalsFile) return ms_SignalsName;

            string searchFile;
            string initialDirectory;

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

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

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

        internal void MS_Stop()
        {
            if (ms_HasSignals)
            {
                this.MediaPositionChanged -= MS_MediaPositionChanged;

                ms_HasSignals = false;
                ms_HasSignalsFile = false;
                ms_HasSignalsPending = false;
                ms_SignalsName = string.Empty;

                ms_CurrentIndex = -1;
                ms_CurrentStart = 0;
                ms_CurrentEnd = 0;

                ms_FileName = string.Empty;
                ms_TimeShift = 0;

                MS_Dispose();
            }
        }

        // returns current media signal
        private void MS_MediaPositionChanged(object sender, PositionEventArgs e)
        {
            if (!ms_SignalsBusy)
            {
                ms_SignalsBusy = true;

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

                if (position < ms_CurrentStart || position >= ms_CurrentEnd)
                {
                    bool backwards = position < ms_CurrentStart;

                    if (MS_Find(position, ms_CurrentIndex, backwards, out ms_CurrentIndex))
                    {
                        ms_CurrentStart = ms_SignalItems[ms_CurrentIndex].StartTime;
                        ms_CurrentEnd = ms_SignalItems[ms_CurrentIndex].EndTime;

                        ms_SignalEventArgs._index = ms_CurrentIndex;
                        ms_SignalEventArgs._message = ms_SignalItems[ms_CurrentIndex].Message;
                        _mediaSignal(this, ms_SignalEventArgs);
                    }
                    else
                    {
                        //if (ms_CurrentIndex < 1) ms_CurrentStart = 0;
                        //else ms_CurrentStart = ms_SignalItems[ms_CurrentIndex].EndTime;

                        //if (ms_CurrentIndex > ms_SignalItems.Count - 2) ms_CurrentEnd = this.Length;
                        //else ms_CurrentEnd = ms_SignalItems[ms_CurrentIndex].StartTime;

                        if (ms_CurrentIndex < 1) ms_CurrentStart = 0;
                        else ms_CurrentStart = ms_SignalItems[ms_CurrentIndex].EndTime;

                        if (ms_CurrentIndex > ms_SignalItems.Count - 1) ms_CurrentEnd = this.Length;
                        else ms_CurrentEnd = ms_SignalItems[ms_CurrentIndex].StartTime;


                    }
                }
                ms_SignalsBusy = false;
            }
        }

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

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

        #endregion


        // ******************************** Media Signals - Find File / Search File

        #region Media Signals - Find File / Search File

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

            SignalSearchData data = new SignalSearchData(fileName);
            MS_SearchFile(initialDirectory, ms_DirectoryDepth, data);
            return data.Found ? data.FilePath.ToString() : string.Empty;
        }

        private void MS_SearchFile(string directory, int depth, SignalSearchData 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++)
                    {
                        MS_SearchFile(directories[i], depth, data);
                        if (data.Found) return;
                    }
                }
            }
            catch { /* ignore */ }
        }

        #endregion


        // ******************************** Media Signals - From File / Count in File / Dispose

        #region Media Signals - From File / Count in File / Dispose

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

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

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

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

                    ms_SignalItems = new List<SignalItem>(count + 10);

                    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)
                                {
                                    ms_SignalItems.Add(new SignalItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, message));
                                    message = "";
                                    readStep = 1;
                                }
                                else message += line + '\r';
                                break;
                        }
                    }
                    if (!error)
                    {
                        if (message != "") ms_SignalItems.Add(new SignalItem(startTime.TotalMilliseconds, endTime.TotalMilliseconds, message));
                        result = true;
                    }
                }
                reader.Close();
            }
            return result;
        }

        // ... also for dimensioning the signals array
        private int MS_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))
                        {
                            //++count;
                            readStep = 1;
                        }
                        break;
                }
            }
            reader.DiscardBufferedData();
            reader.BaseStream.Seek(0, SeekOrigin.Begin);
            reader.BaseStream.Position = 0;
            return error ? 0 : count;
        }

        private void MS_Dispose()
        {
            if (ms_SignalItems != null)
            {
                for (int i = 0; i < ms_SignalItems.Count; i++)
                {
                    ms_SignalItems[i] = null;
                }
                ms_SignalItems = null;
            }
        }

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