Click here to Skip to main content
15,886,110 members
Articles / Programming Languages / Visual Basic

Fun with Video

Rate me:
Please Sign up or sign in to vote.
4.87/5 (27 votes)
6 Oct 2016CPOL16 min read 57.7K   34.7K   65   10
Playing your favorite video all at the same time

Image 1

Introduction

In my previous article Fun with Sound, we look at using winmm.dll mciSendString API to play multiple sound files at the same time. In the same spirit of having fun, we will do it this time with video, playing multiple video or playing the same video asynchronously in different display areas.

Background

Video files comes in different formats. Some of the most common format are: mpg, mp4, wmv, mov, rmvb. These files can have embedded sound track or no sound track. The main difference in each file format is the encoding used to encode and compress the series of images and sound data.

One of the most common format is mp4, as it can have very high compression ratio and yet maintain reasonable image quality. This makes it very popular as an encoding standard to capture video in mobile devices.

To play back audio and video, we need to decode the data from the video files, thus we need codec (coder-decoder) software. Codec software may be application specific or generic to be supported by common interface/platform like OpenGL or DirectShow.

In Windows (XP, 7 and 8), the support for DirectShow is very good with lots of codecs and filters that comes with the OS. Using DirectShow, we should be able to playback most of the common video and audio file format: mpg, wmv, wma, avi, animated gif. However generic DirectShow codecs for some very popular video format like mp4 and rmvb do not come with the OS and it looks like Microsoft does not even provide such in their download sites.

However, these codecs are quite easily available from third parties download sites. One of such sites that provide a relatively hassle-free download is http://www.windows7codecs.com/

The download from this site provides DirectShow codecs for most of the recent popular video formats including mp4 and rmvb.

If you have installed DirectShow SDK, you will have installed a very useful utility known as GraphEdit. To check if your DirectShow installation support a file type, just drag into GraphEdit a sample of the file type. The figure below shows the data-flow graph created when I drag a mp4 file into it.

Image 2

The graph would be created provided GraphEdit is able to find all the DirectShow codecs and and filters to process and render such files. If you have not installed the required codecs and filters, no graph would be created and GraphEdit pops up an error message.

The reason for the discussion on DirectShow is because mciSendString is actually making use of DirectShow to playback the video and audio files.

If you have a file type that does not work with GraphEdit, then it would not likely work with mciSendString.

For this article, I have included some animated gif and mp4 files with the zip source distribution pack. However you can also use the mp4 files recorded from your IOS, Andriod or Windows Phone devices. Just copy these files into the current directory where MCIDemo.exe resides. Note that you would need ffd Video Decoder obtainable from http://www.windows7codecs.com/ to play mp4 files

Using the code

The code below is from the class definition of MciPlayer. MCISendString(string Pcommand) is a very thin wrapper for the winmm.dll mciSendString() API.

C#
[DllImport("winmm.dll")]
private static extern int mciSendString(String strCommand, StringBuilder strReturn, int iReturnLength, IntPtr hwndCallback);
[DllImport("winmm.dll")]
public static extern int mciGetErrorString(int errCode, StringBuilder errMsg, int buflen);

public string MCISendString(string Pcommand)
{
    StringBuilder sb = new StringBuilder(1024);
    int ret = mciSendString(Pcommand, sb, sb.Capacity, IntPtr.Zero);
    if (ret == 0)
        return sb.ToString();
    else
    {
        mciGetErrorString(ret, sb, sb.Capacity);
        return sb.ToString();
    }
}
VB.NET
<DllImport("winmm.dll")> _
Private Shared Function mciSendString(strCommand As [String], strReturn As StringBuilder, iReturnLength As Integer, hwndCallback As IntPtr) As Integer
End Function
<DllImport("winmm.dll")> _
Public Shared Function mciGetErrorString(errCode As Integer, errMsg As StringBuilder, buflen As Integer) As Integer
End Function

Public Function MCISendString(Pcommand As String) As String
    Dim sb As New StringBuilder(1024)
    ' Pcommand = Pcommand.Replace("@@", Alias);
    Dim ret As Integer = mciSendString(Pcommand, sb, sb.Capacity, IntPtr.Zero)
    If ret = 0 Then
        Return sb.ToString()
    Else
        mciGetErrorString(ret, sb, sb.Capacity)
        Return sb.ToString()
    End If
End Function

To open a video file, we create an instance of MciPlayer class. We then formulate the string that we want to send. In this case we use the open command string:

"open mpegvideo!\"" + Application.StartupPath + "\\test-mpeg_512kb.mp4\" alias " + _index 

If we replaces all the variables with some sample values:

"open mpegvideo!\"c:\\test-mpeg_512kb.mp4\" alias 1" 

We open the file c:\test-mpeg_512kb.mp4 with 1 as alias. Note that we append mpegvideo! before the filename, this is to tell the MCI system that the file is a digitalvideo file type.

C#
string Pcommand = "";
MciPlayer m = new MciPlayer();
Pcommand = "open mpegvideo!\"" + Application.StartupPath + "\\test-mpeg_512kb.mp4\" alias " + _index;
m.MCISendString(Pcommand);
VB.NET
 Dim Pcommand As String = ""
 Dim m As New MciPlayer()
 Pcommand = "open mpegvideo!""" & filename & """ alias " & _index 
 m.MCISendString(Pcommand)

After we have open (load) the file associated with an alias, we can subsequently use the alias in place of the filename to refer to the media file. Note that the alias must be unique for each and every file opened. However we can open the same file with different alias, as long as each alias always refer to only one partcular file.

The MCISendString() command can also be used to run query. For example the command string  status 0 mode queries the the playing status of the media file with alias 0. The return from MCISendString() for this command string could be "playing", "paused" or "stopped".

Command Strings

For all MCI commands refer to Multimedia Command Strings. However not all the different commands work for video files, and for each command, only a small subset of the operations may be supported by your MCI system.

I woud categorize the commands based on the following common tasks

  • Loading a video file
  • Displaying the video file
  • Playing the video file
  • Tracking the video file
  • Getting info from the video file
  • The set command
  • Unloading the video file

Loading a video file

We use the open command to load a video file to the MCI system. The simpliest way to use the open command for digital video is to load the media file and just assigned it with an alias

For example: open c:\mymedia.mpg alias 0. 

The MCI system will try to make sense of the file type and assigned it with the alias 0. However since we are dealing with video file, we can assist the MCI system by appending the file name with the device name. For all the video/audio type, we can use the device name mpegvideo. Thus for the above example, we will use:

open mpegvideo!c:\mymedia.mpeg alias 0. 

The open command can also assigned to load a file and use a assigned parent window for display rendering

open mpegvideo!c:\mymedia.mpg.mpg alias 0 parent  10024 style child; 

In the above command, we load the file c:\mymedia.mpg.mpg with an alias 0 and assigned the window handle 10024 as the parent for the display window that would have a style as a child of the parent window.

When the command is run sucessfully, mciSendString will return 0 (SUCCESS). Any other values returned are error code. To get the description of the error code, we use the mciGetErrorString(), passing in the error code. For example error code 275 is for "file not found". For a list of error code refer to Error Messages (Multimedia MCI Control)

Displaying the video file

For rendering video file, we can assigned it a parent window handle using the open command. If no default parent window handle is assigned, MCI system will create a default window when the file is first played. The default is a popped up sizable window, sized up to the video resolution of the media file. If the resolution of the video is VGA(640x480), the default window will have a client area of 640x480 pixels. The window is sizable and the video displayed is scaled to maintained its aspect ratio.

In our demo, we assigned the window handle of the panel as the parent window and the display window created will be a child window of the panel.

Panel1 is used as the main area to display up to 4 video. Panel1 is 640x480 and I have divided it into 4 sub-areas: top-left, top-right, bottom-left, bottom-right. For video displayed at top-left, it would be assigned an alias of 0, the top-right an alias of 1, bottom-left 2 and bottom-right 3.

close 0
open mpegvideo!"test-mpeg_512kb.mp4" alias 0 parent 655830 style child
where 0 source
-->0 0 320 240
put 0 window at 10 80 300 225

The above script shows a sequence of command that is used to display and render a video file to one of our panel's sub-area.

We first close all resources referred to by alias 0 as we want to make use of this alias to identify the file that we want to load.

close 0

We then load the file with the open command assigning panel as the parent and using 0 as the alias.

open mpegvideo!"test-mpeg_512kb.mp4" alias 0 parent 655830 style child

Then we get the video resolution of the source video, This is for us to calculate the aspect ratio, so that we maintain the aspect ratio in our display for the video. In this case the video resolution is 320x240.

where 0 source
-->0 0 320 240

We use our function getAspectRatioSize() to get the size of the display that we would want to allocate for the video, in this case the calculated area is 300x225. And we position the window display area at coordinate 10,80. All these values are relative to the window identified by the window handle, in this case panel1. The next put command tells the MCI system to use this display area

put 0 window at 10 80 300 225

Playing the video

To play the video, use the play command. There are options:

Play the file alias 0 in a loop, starting all over again once it reaches the end

play 0 repeat 

Play the file alias 0 from the postion 0

play 0 from 0

Play the file alias 0 from current position that it has paused

play 0

To pause the playing of the file alias 0

pause 0

To resume from where we paused for the file alias 0

resume 0

To stop the playing of the file alias 0

stop 0

To reposition video current frame for file alias 0 to the position 1000ms from start.

seek 0 1000

Note that re-postitoning the current frame may pause the video. We may have to resume play with the play command.

Refer to the MSDN documentation Multimedia Command Strings. for more options

Tracking the video

To track the video play we would need information on the status of the play and the current position of the frame being display. To do this we use the very versatile status command

To find out the mode of play for the file alias 0. This returns the status as "playing", "pause" and "stopped"

status 0 mode

To get the current frame being played for file alias 0. This returns a number either in terms of frame number or in terms of time elapsed in millisec.

status 0 position

To get the length of the video file alias 0. Again, it is in terms of number of frames or time in millisec

status 0 length

To track the current playing of the video, we can take the ratio position/length. In our demo, we track the video play using a timer calling the above 2 commands every 500ms. The ratio postion/length is calculated as a percentage and the figure is used to position the tracker bar for each running video

Getting information about the video

To get information relating to display window, we use the where command

To get the video resolution of the file alias 0. The return value from a where command is rectangle defined by x1 y1 width height, for example 0 0 200 300 mean a rectangle at coordinates 0,0 and size 200x300. Send the string below to MCISendString() and the return string would be 0 0 200 300 if the width of the video is 200 and its height 300

where 0 source

To get the rectangle that is displaying the video for file alias 0

where 0 destination

For most other information we use the status command

To get the window handle that is currently displaying the video for file alias 0. This is useful if we want to do a screen capture of the display.

status 0 window handle

To find out about the loudness of the playback of the audio track in the file alias 0.

status 0 volume 

We can then use the setaudio command to subsequently adjust the volume. The range of values to setaudio volume is 0 to 1000.

setaudio 0 volume to 500

To get more technical info, we use the info command

To get product (device) information on file alias 0. I have tested this for file type: gif, mpg, mp4, wav, mp3, that is taged with mpegvideo at the open command, the info <alias> product command always return "DirectShow" in my system, so I suspected that DirectShow is the underlying technology used in winmm.dll

info 0 product

The set command

The set command can be used to make changes to audio and video playback.

Setting audio left/right/all on/off. The command below sets the left channel of the media file alias 0 on

set 0 audio left on

Setting video playback speed. The command below sets the playback speed to 50% of normal playback speed

set 0 speed 500

Setting the time format. The default time format of most video file is milliseconds. However, the time format can be set to either frames/milliseconds. The command below sets the time format to frames

set 0 time format frames

 

Unloading the video file

Use the close command.

To close the file alias 0

close 0

Or to close all file that is loaded

close all

Once the close command is run successfully, the file is unloaded from the MCI system, and we can reuse the alias that we has previously use. In our demo, I use alias 0,1 ,2 and 3 for each of the 4 quadrants in the panel.

If any of the file identified by these alias are closed, I reuse these alias to load the next video file, otherwise I just close the oldest file that was loaded and reclaim its alias for loading the new file.

Demo

Image 3

The top left listbox list all the media files in the current directory at startup. Currently I list all ".mpg", ".gif", ".mp4" files, For your system, you may want to test with DirectShow GraphEdit to find out all video and audio file type that your system support and edit the the form load handler function to load these files.

Choose a file from the listbox and click the Play Loop button. The chosen file will first be loaded into the default window (it happens in a flash and may not be noticeable) and then the display get pasted to the first available sub-area in the right panel.

Each sub-area in the panel is composed of

  • Label
  • Audio on/off checkbos
  • Tracker bar
  • Pause/Play button
  • Close button
  • Video display

The label's text shows these information

  1. the alias,
  2. the length of the video,
  3. the file name

If you do a mouse left double click on this label, the video display will popped out to its viewing window. The viewing window is sizeable, so you can resize it for better viewing of the video. Double clicking again on the label will cause the video display to be back into the sub-area in the panel. You can also close the viewing window and it will also be displayed back into the panel.

Image 4

The features of the viewing window:

  • Full screen toggle
  • Audio on/off
  • Scroll forward/backward
  • Screen Capture
  • Pause/Resume

These are the shortcuts to activate the features while the viewing window is active:

Full screen toggle: F11

Audio on/off: Left mouse click

Scroll forward/backwork: Left<key to go backward and Right>key to go forward. Each time the key is release, 2 % of the frames are skipped

Screen Capture: Right mouse click

Pause/Resume: press <space> bar

Now back to the panel controls:

The "Audio on?" checkbox toggles the audio on/off for the display.

Note that as you click these UI components, the Command Log textbox is updated with every MCI command that is send to the MCI system. This is a good learning tool to learn about the commands that are used to carry out each task. You can also directly type some MCI commands into the Current Command textbox, one line for each command, Then click the Send String button.

There is a label at that bottom that serves to display the status of the command executed. The command is executed correctly if nothing is displayed in the status label, otherwise there will be a descriptive error string.

To reposition the video play, drag the track bar. The video display will show the frame at the chosen position. You will need to click the pause/play button to continue viewing the video from the chosen position.

To unload the video, click the Close button

Video Overlay

Image 5

In version 3, I have added overlay over the video display window. This feature is implemented via the coupling of FormDisplay and FormTransparent.

For FormDisplay, we add in these codes:

C#
public FormTransparent fTp=new FormTransparent();

fTp._frmdisplay = this;

this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.TopMost = true;


     private void FormDisplay_VisibleChanged(object sender, EventArgs e)
     {
         if(fTp!=null)
            fTp.Visible = this.Visible;

     }

     private void FormDisplay_Activated(object sender, EventArgs e)
     {

         if (fTp != null)
         {

             fTp.Visible = true;
             fTp.BringToFront();
             fTp.Focus();

         }
     }
VB.NET
Public fTp As New FormTransparent()
fTp._frmdisplay = Me
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
Me.TopMost = True

Private Sub FormDisplay_VisibleChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.VisibleChanged
    If fTp IsNot Nothing Then
        fTp.Visible = Me.Visible
    End If

End Sub

Private Sub FormDisplay_Activated(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Activated

    If fTp IsNot Nothing Then

        fTp.Visible = True
        fTp.BringToFront()

        fTp.Focus()
    End If
End Sub

For FormTransparent:

C#
 private Image logo;
 public string statusText = "";
 public string subtitleText = "";

public FormDisplay _frmdisplay=null;

this.TopMost = true;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Sizable;
this.TransparencyKey = System.Drawing.Color.White;

        private void FormTransparent_LocationChanged(object sender, EventArgs e)
        {
            if (!isFirstActivated) return;

            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
            if (_frmdisplay != null)
                // _frmdisplay.Location = this.Location;
                _frmdisplay.Location = new Point(this.Left + rect.Left, this.Top + rect.Top);
        }

        private void FormTransparent_Resize(object sender, EventArgs e)
        {
            if (!isFirstActivated) return;
            if (_frmdisplay != null)
            {
                System.Diagnostics.Debug.WriteLine("fTp resize " + _nextnum++);

                Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
                _frmdisplay.Size = new Size(rect.Width, rect.Height);
                _frmdisplay.Refresh();

            }
        }


        private void FormTransparent_KeyPress(object sender, KeyPressEventArgs e)
        {
            _frmdisplay.FormDisplay_KeyPress(sender, e);
        }

        private void FormTransparent_KeyUp(object sender, KeyEventArgs e)
        {
            _frmdisplay.FormDisplay_KeyUp(sender, e);
        }

        private void FormTransparent_FormClosing(object sender, FormClosingEventArgs e)
        {

            _frmdisplay.FormDisplay_FormClosing(sender, e);
 
        }

        private void FormTransparent_VisibleChanged(object sender, EventArgs e)
        {
            if (this.Visible)
            {

                this.BringToFront();

            }
        }

        private void FormTransparent_Activated(object sender, EventArgs e)
        {
            if (_frmdisplay == null) return;

            this.Text = _frmdisplay.Text;
            _frmdisplay.BringToFront();
            this.BringToFront();

            
            if (isFirstActivated) return;
            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);

            //Set the location and size of the transparent window
            //such the the display window will 
            //occupy exactly the client area of the transparent window
            this.Top = _frmdisplay.Top - rect.Top;
            this.Left = _frmdisplay.Left - rect.Left;
            this.Width = _frmdisplay.Width + (rect.Left *2);
            this.Height = rect.Top + _frmdisplay.Height + (rect.Left); 

            //_nextnum++;
            //System.Diagnostics.Debug.WriteLine("frm ftp activated " + _nextnum +
            //  _frmdisplay.Location.ToString() + "," + _frmdisplay.Size.ToString() + " " +
            //  this.Location.ToString() + "," + this.Size.ToString());
            isFirstActivated = true;
            
        }



        private void FormTransparent_Paint(object sender, PaintEventArgs e)
        {
 
            e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(255, 255, 254)), 
                                                    new Rectangle(0, 0, this.Width , this.Height ));

            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
            //e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0, rect.Width-1 , rect.Height-1));
            if (statusText != "")
            {

                StringFormat format = new StringFormat();
                format.LineAlignment = StringAlignment.Center;
                format.Alignment = StringAlignment.Far;

                e.Graphics.DrawString(statusText, new Font("Courier", 10), Brushes.Yellow ,new RectangleF(0,0,rect.Width,20),format);

            }

            if (subtitleText != "")
            {
                StringFormat format = new StringFormat();
                format.LineAlignment = StringAlignment.Center;
                format.Alignment = StringAlignment.Center;
                e.Graphics.DrawString(subtitleText, new Font("Courier", 10), Brushes.Yellow, new RectangleF(0, rect.Height-50, rect.Width, 40), format);

            }

            if (logo != null)
                e.Graphics.DrawImage(logo, new Rectangle(0, 0, logo.Width , logo.Height), new Rectangle(0, 0, logo.Width, logo.Height), GraphicsUnit.Pixel);
                      
        }
VB.NET
Public _frmdisplay As FormDisplay = Nothing
Me.TopMost = True
Me.TransparencyKey = System.Drawing.Color.White

Private Sub FormTransparent_Activated(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Activated
    If _frmdisplay Is Nothing Then
        Return
    End If

    Me.Text = _frmdisplay.Text
    _frmdisplay.BringToFront()
    Me.BringToFront()

    If isFirstActivated Then
        Return
    End If
    Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)

    'Set the location and size of the transparent window
    'such the the display window will 
    'occupy exactly the client area of the transparent window
    Me.Top = _frmdisplay.Top - rect.Top
    Me.Left = _frmdisplay.Left - rect.Left
    Me.Width = _frmdisplay.Width + (rect.Left * 2)
    Me.Height = rect.Top + _frmdisplay.Height + (rect.Left)

    'Minimum width correction
    If (Me.Width) <= 132 Then
        _frmdisplay.Width = 132 - (rect.Left * 2)
        _frmdisplay.Refresh()
    End If

    isFirstActivated = True

End Sub

Private Sub FormTransparent_LocationChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.LocationChanged
  If Not isFirstActivated Then
      Return
  End If

  Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
  If _frmdisplay IsNot Nothing Then
      ' _frmdisplay.Location = this.Location;
      _frmdisplay.Location = New Point(Me.Left + rect.Left, Me.Top + rect.Top)
  End If
End Sub

Private Sub FormTransparent_Resize(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Resize
  If Not isFirstActivated Then
      Return
  End If
  If _frmdisplay IsNot Nothing Then
      System.Diagnostics.Debug.WriteLine("fTp resize " & System.Math.Max(System.Threading.Interlocked.Increment(_nextnum), _nextnum - 1))

      Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
      _frmdisplay.Size = New Size(rect.Width, rect.Height)

      _frmdisplay.Refresh()
  End If
End Sub

Private Sub FormTransparent_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles MyBase.KeyPress
  _frmdisplay.FormDisplay_KeyPress(sender, e)
End Sub

Private Sub FormTransparent_KeyUp(ByVal sender As Object, ByVal e As KeyEventArgs) Handles MyBase.KeyUp
  _frmdisplay.FormDisplay_KeyUp(sender, e)
End Sub

Private Sub FormTransparent_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles MyBase.Paint

  e.Graphics.FillRectangle(New SolidBrush(Color.FromArgb(255, 255, 254)), _
                           New Rectangle(0, 0, Me.Width, Me.Height))

  Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
  'e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0, rect.Width-1 , rect.Height-1));
  If statusText <> "" Then

      Dim format As New StringFormat()
      format.LineAlignment = StringAlignment.Center
      format.Alignment = StringAlignment.Far
      e.Graphics.DrawString(statusText, New Font("Courier", 10), Brushes.Yellow, New RectangleF(0, 0, rect.Width, 20), format)
  End If

  If subtitleText <> "" Then
      Dim format As New StringFormat()
      format.LineAlignment = StringAlignment.Center
      format.Alignment = StringAlignment.Center

      e.Graphics.DrawString(subtitleText, New Font("Courier", 10), Brushes.Yellow, New RectangleF(0, rect.Height - 50, rect.Width, 40), format)
  End If

  If logo IsNot Nothing Then
      e.Graphics.DrawImage(logo, New Rectangle(0, 0, logo.Width, logo.Height), New Rectangle(0, 0, logo.Width, logo.Height), GraphicsUnit.Pixel)
  End If

End Sub

Transparency in FrmTransparent is implemented via the use of the Form's TransparencyKey property. Any part of the form that has the same color as TransparencyKey property would not be rendered, instead the pixels for that part of the form below in the z-order are rendered. And since most parts of FrmTransparent is set to the color of the TransparencyKey, it is basically transparent, except for the logo, status text and caption text which are not of that color.

C#
this.TopMost = true;
this.TransparencyKey = System.Drawing.Color.FromArgb(255, 255, 254);
VB.NET
Me.TopMost = True
this.TransparencyKey = System.Drawing.Color.FromArgb(255,255,254)         

The idea is to have FormTransparent be always just in front of FormDisplay in the z-order, and in such a way that FormDisplay would never have keyboard focus. These events handlers in FormDisplay and FormTransparent ensure this requirement:

  • FormDisplay_VisibleChanged
  • FormDisplay_Activated
  • FormTransparent_VisibleChanged
  • FormTransparent_Activated

Since now FormDisplay has FormBorderStyle.none , we would not be able to relocated or resized it using the mouse. We depend on these event handlers in FormTransparent to do that:

  • FormTransparent_Resize
  • FormTransparent_LocationChanged

Furthermore, since FormDisplay cannot have keyboard focus, we cannot use some of those short-cuts that we have defined for it, which require either keyboard focus or mouse capture. Again we take care of these in the following event handlers in FormTransparent. Basically FormTransparent serves as a proxy for FormDisplay to channel the keyboard activities to call original event handlers in FormDisplay:

  • FormTransparent_KeyPress
  • FormTransparent_KeyUp

Finally, the main reason for all these redirections: to add in the status, logo, and sub-titles, this is done via the FormTransparent_Paint event handler. This Paint event is called every time FormTransparent is refreshed.

These variables below are used in the FormTransparent_Paint handler and is updated by FrmMain in the timer1 event handler:

  • statusText 
  • subtitleText
C#
        //update the trackbar to show video play position while video is playing
        private void timer1_Tick(object sender, EventArgs e)
        {

            timer1.Enabled = false;

            MciPlayer p = new MciPlayer();
            string Pcommand = "";
            for (int i = 0; i < 4; i++)
            {
                if (!availnum[i])
                {
                    Pcommand = "status " + i + " mode";
                    string s1 = p.MCISendString(Pcommand);
                    //we remove the logging as it may be too frequent and it cluster up the logs
                    //LogPcommand(Pcommand);
                    //LogPcommand("--->" + s1);
                    if (s1 == "playing")
                    {
                        Pcommand = "status " + i + " position";
                        s1 = p.MCISendString(Pcommand);
                        //LogPcommand(Pcommand);
                        //LogPcommand("--->" + s1);
                        int ipos = int.Parse(s1);

                        Pcommand = "status " + i + " length";
                        string s2 = p.MCISendString(Pcommand);
                        //LogPcommand(Pcommand);
                        //LogPcommand("--->" + s1);
                        int ilen = int.Parse(s2);
                        int ipc = (int)((float)(ipos * 100) / (float)ilen);

                        trkMedia[i].Value = ipc;
                        trkMedia[i].Refresh();

                        string s3 = "";

                        s3 = GetSubTitleText(i, ipos);

                        if(frmDisplay[i]!=null)
                            if (frmDisplay[i].fTp != null)
                            {
                                frmDisplay[i].fTp.statusText = s1 + " " + s2;
                                frmDisplay[i].fTp.subtitleText = s3;
                                frmDisplay[i].fTp.Refresh();

                            }

                    }
                }
            }
          timer1.Enabled = true;
        }
VB.NET
'update the trackbar to show video play position while video is playing
Private Sub timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles timer1.Tick

timer1.Enabled = False

Dim p As New MciPlayer()
Dim Pcommand As String = ""
For i As Integer = 0 To 3
    If Not availnum(i) Then
        Pcommand = "status " & i & " mode"
        Dim s1 As String = p.mciSendString(Pcommand)
        'we remove the logging as it may be too frequent and it cluster up the logs
        'LogPcommand(Pcommand);
        'LogPcommand("--->" + s1);
        If s1 = "playing" Then
            Pcommand = "status " & i & " position"
            s1 = p.mciSendString(Pcommand)
            'LogPcommand(Pcommand);
            'LogPcommand("--->" + s1);
            Dim ipos As Integer = Integer.Parse(s1)

            Pcommand = "status " & i & " length"
            Dim s2 As String = p.mciSendString(Pcommand)
            'LogPcommand(Pcommand);
            'LogPcommand("--->" + s1);
            Dim ilen As Integer = Integer.Parse(s2)
            Dim ipc As Integer = CInt(Math.Truncate(CSng(ipos * 100) / CSng(ilen)))

            trkMedia(i).Value = ipc
            trkMedia(i).Refresh()

            'this.mouse_down = saved_mousedown;
            'update with caption file

            Dim s3 As String = ""

            s3 = GetSubTitleText(i, ipos)

            If frmDisplay(i) IsNot Nothing Then
                If frmDisplay(i).fTp IsNot Nothing Then
                    frmDisplay(i).fTp.statusText = s1 & " " & s2
                    frmDisplay(i).fTp.subtitleText = s3

                    frmDisplay(i).fTp.Refresh()
                End If

            End If
        End If
    End If
Next
timer1.Enabled = True
End Sub

Subtitle

Subtitle for the video is implemented by using a text file that has the same name as the video file and is located in the same directory. The extension used is ".cap.txt". This file is saved as a unicode text file so that we can input in text in other languages other than the Latin-based languages.

The format is very simple:

1137@#@<taj mahal, india>
2358@#@<guy making a call>
3533@#@<street view>

The text file is "\r\n" delimited. The content for each line is delimited by "@#@". The part on the left of this delimiter shows the time(ms) or frame number when the subtitle text on the right of the delimiter should appear.

The timer in FormMain uses the function GetSubTitleText() function below to find the subtitle to display based on the current time/frame.

C#
private string GetSubTitleText(int alias, int pos)
{
    if (subtitle[alias] == "") return "";

    string[] subtitles=subtitle[alias].Split(new string[] { "\r\n" },StringSplitOptions.None);
    for (int i = subtitles.Length-1; i >=0; i--)
    {
        var subtitle1=subtitles[i].Split(new string[] { "@#@" },StringSplitOptions.None);
        if (subtitle1.Length<2) continue;
        try
        {
            int subpos = int.Parse(subtitle1[0]);
            if ( subpos<=pos)
            {
                if (subtitle1.Length >=1)
                    return (subtitle1[1]);
                else
                    return "";
            }

        }
        catch { return ""; }
    }
    return "";
}
VB.NET
Private Function GetSubTitleText(ByVal [alias] As Integer, ByVal pos As Integer) As String
    If subtitle([alias]) = "" Then
        Return ""
    End If

    Dim subtitles As String() = subtitle([alias]).Split(New String() {vbCr & vbLf}, StringSplitOptions.None)
    For i As Integer = subtitles.Length - 1 To 0 Step -1
        Dim subtitle1 = subtitles(i).Split(New String() {"@#@"}, StringSplitOptions.None)
        If subtitle1.Length < 2 Then
            Continue For
        End If
        Try
            Dim subpos As Integer = Integer.Parse(subtitle1(0))
            If subpos <= pos Then
                If subtitle1.Length >= 1 Then
                    Return (subtitle1(1))
                Else
                    Return ""
                End If

            End If
        Catch
            Return ""
        End Try
    Next
    Return ""
End Function

Points of Interest

The Command Log and the Current Command execution feature is very useful for you to learn more about using the various MCI commands. In fact, most of the test done to validate the ideas in this article is via these 2 features.

With Version 2, we have an almost full featured viewing window. With Full Screen mode, it seems like you are playing from a commericial media player. In fact, we are better, we view one and keep 3 others in the panel, ready to be launched into full view readily.

With Version 3, you can add in subtitle to your video.

Have fun!

History

12 June 2014: Fun with Video V1

13 June 2014: Fun with Video V2

Make the Demo more user friendly.

16 June 2014: Fun with Video V3a

Add in Transparent overlay to show logo, status and subtitle, fixes some bugs relating to mouse capture.

Add in additional VB.Net codes tab sections and source download  in VB.Net

5 Oct 2016: Fun with Video V3b 

Set transparency key to Color(255,255,254) instead of White. This will disable mouse events to pass through the transparent form. Mouse events will be handled at the transparent form. The  result is more stable mouse handling when resizing and moving the transparent form.

 

License

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


Written By
Software Developer (Senior)
Singapore Singapore
Coder. Hacker. Fixer.

Comments and Discussions

 
QuestionUmmm Pin
#realJSOP5-Oct-16 6:52
mve#realJSOP5-Oct-16 6:52 
QuestionSetting the time format if caption files does not work Pin
Yang Kok Wah21-Dec-15 0:52
Yang Kok Wah21-Dec-15 0:52 
GeneralMy vote of 5 Pin
User 110609795-Oct-15 7:58
User 110609795-Oct-15 7:58 
GeneralMy vote of 5 Pin
Agent__00717-Oct-14 1:25
professionalAgent__00717-Oct-14 1:25 
GeneralPretty cool Pin
Thomas L Wilbur16-Jun-14 12:59
Thomas L Wilbur16-Jun-14 12:59 
GeneralMy vote of 5 Pin
Volynsky Alex15-Jun-14 23:31
professionalVolynsky Alex15-Jun-14 23:31 
GeneralRe: My vote of 5 Pin
Yang Kok Wah16-Jun-14 0:20
Yang Kok Wah16-Jun-14 0:20 
GeneralRe: My vote of 5 Pin
Volynsky Alex16-Jun-14 0:24
professionalVolynsky Alex16-Jun-14 0:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.