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.
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
.
[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();
}
}
<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)
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.
string Pcommand = "";
MciPlayer m = new MciPlayer();
Pcommand = "open mpegvideo!\"" + Application.StartupPath + "\\test-mpeg_512kb.mp4\" alias " + _index;
m.MCISendString(Pcommand);
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
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
- the alias,
- the length of the video,
- 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.
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
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:
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();
}
}
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
:
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 = 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);
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);
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);
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);
}
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)
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)
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 = 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)
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.
this.TopMost = true;
this.TransparencyKey = System.Drawing.Color.FromArgb(255, 255, 254);
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:
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);
if (s1 == "playing")
{
Pcommand = "status " + i + " position";
s1 = p.MCISendString(Pcommand);
int ipos = int.Parse(s1);
Pcommand = "status " + i + " length";
string s2 = p.MCISendString(Pcommand);
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;
}
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)
If s1 = "playing" Then
Pcommand = "status " & i & " position"
s1 = p.mciSendString(Pcommand)
Dim ipos As Integer = Integer.Parse(s1)
Pcommand = "status " & i & " length"
Dim s2 As String = p.mciSendString(Pcommand)
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()
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.
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 "";
}
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.