Click here to Skip to main content
Click here to Skip to main content

Video Wall with MCIPlayerControl

, 1 Jul 2014
Rate this:
Please Sign up or sign in to vote.
Using a simple custom made video player control to display a video wall

Introduction

Video walls are everywhere. They add colors to city life. In this article, we would be creating a video wall using our custom made video player control.

Background

This article is based on ideas from my earlier article Fun with Video. In that article, we explore how to make use of mciSendString API to play video files. Here we encapsulate all main functionalities of video display and playing into a simple Window Form control. With the video functionalities encapsulated, we do not have to worry about the intricacies of the mcisSendString command and just concentrate on making use of the control to create something fun and useful.

I have included some animated gif and mp4 files in the downland zip. To be able to playback these files, you may need to get the codec from http://www.windows7codecs.com/

Beside the obvious use of video wall for advertisement, we can also use it for

  • forensic video analysis
  • motion study

Video analysis of a train accident

A dog in high speed motion

The MCIPlayerControl

Our MCIPlayerControl exposes the following properties

public bool ShowControls
public string Mediafilename
public string Caption
public int Alias

Public Property ShowControls() As Boolean
Public Property Mediafilename() As String
Public Property Caption() As String
Public Property [Alias]() As Integer

 

The following methods are for loading and closing of the media files

public bool LoadMediaFile()
public bool LoadMediaFile(string filename, int alias,bool resize)
public void CloseMediaFile()

 

Public Function LoadMediaFile() As Boolean
Public Function LoadMediaFile(filename As String, alias__1 As Integer, resize As Boolean) As Boolean
Public Sub CloseMediaFile() 

These methods control video play and audio

public void Pause_Play()
public void PlayLoop()
public void ToggleAudio()
public void SeekPlayPosition(int pos)
Public Sub Pause_Play()
Public Sub PlayLoop()
Public Sub ToggleAudio()
Public Sub SeekPlayPosition(pos As Integer)  

To make use of the MCIPlayerControl, you can either drag it into the form at design time or call its default constructor

MCIControl.MCIPlayerControl()

 

UI Controls

The default MCIPlayerControl shows the following UI controls 

 Seek Start Postion

 Pause/Resume/Play

 Seek End Position

 Toogle Play Slow Motion 

 Toggle Fast Forward

 Toggle Audio On/Off

Pop Up Viewer Window

 Close Video

 Scroll to Video Frame

 

If the ShowConrols property is set to false, then these U controls will not be visible. Without UI controls, we will be able to resize the MCIPlayerControl. This is useful if we want to display an array of MCIPlayerControls to fit into a form or panel. For example, to create a video wall, we make use of an array of resizable MCIPlayerControls without UI controls.

 

Coding a Sample Video Wall

The code below shows how we make use of an array of MCIPlayerControls to display a video wall.

            if (listBox1.SelectedIndex < 0) return;
            this.WindowState = FormWindowState.Maximized;
            this.FormBorderStyle = FormBorderStyle.None;
            panel1 = new Panel();
            this.Controls.Add(panel1);

            this.panel1.Location = new Point(0, 0);
            Rectangle rect=ScreenCapture.GetClientRect(this.Handle);
            this.panel1.Size = new Size(rect.Width, rect.Height);
            this.panel1.Visible=true;
            this.panel1.BringToFront();
            //mciPlayerControl1.CloseMediaFile();
            buttonClose1_Click(null, null);
            mciPlayerControl2.CloseMediaFile();    

            int s_width = panel1.Width / 3;
            int s_height = panel1.Height / 3;
            MCIControl.MCIPlayerControl testmc = new MCIControl.MCIPlayerControl();
            panel1.Controls.Add(testmc);
            testmc.Location = new Point(0, 0);
            testmc.Size = new Size(s_width, s_height);
            testmc.ShowControls = false;
            testmc.Visible = true;
            testmc.Alias = 0;
            testmc.Mediafilename = listBox1.Items[listBox1.SelectedIndex].ToString();
            testmc.LoadMediaFile();
            int n_height = testmc.Height, n_width = testmc.Width ;
            testmc.PlayLoop();
            int next_alias = 1;
            int xpos = n_width , ypos = 0;
            while (ypos < panel1.Height)
            {
                while (xpos < panel1.Width)
                {
                    MCIControl.MCIPlayerControl newmc = new MCIControl.MCIPlayerControl();
                    panel1.Controls.Add(newmc );
                    newmc.Location = new Point(xpos, ypos);
                    newmc.ShowControls = false;
                    newmc.Size = new Size(n_width, n_height);

                    newmc.Visible = true;
                    newmc.Alias = next_alias ;
                    newmc.Mediafilename = listBox1.Items[listBox1.SelectedIndex].ToString()
                    newmc.LoadMediaFile();
                    newmc.PlayLoop();
                    newmc.ToggleAudio();
                    next_alias++;
                    xpos += (n_width);

                }
                ypos += (n_height);
                xpos = 0;
            }

            panel1.Focus();
          If listBox1.SelectedIndex < 0 Then
                Return
            End If
            Me.WindowState = FormWindowState.Maximized
            Me.FormBorderStyle = FormBorderStyle.None
            panel1 = New Panel()
            Me.Controls.Add(panel1)

            Me.panel1.Location = New Point(0, 0)
            Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
            Me.panel1.Size = New Size(rect.Width, rect.Height)
            Me.panel1.Visible = True
            Me.panel1.BringToFront()
            'mciPlayerControl1.CloseMediaFile();
            buttonClose1_Click(Nothing, Nothing)
            mciPlayerControl2.CloseMediaFile()

            ' divide to 3 columns
            Dim s_width As Integer = panel1.Width \ 3 
            Dim s_height As Integer = panel1.Height \ 3 
            Dim testmc As New MCIControl.MCIControl.MCIPlayerControl()
            panel1.Controls.Add(testmc)
            testmc.Location = New Point(0, 0)

            testmc.Size = New Size(s_width, s_height)
      
            testmc.Visible = True
            testmc.[Alias] = 0
            testmc.Mediafilename = listBox1.Items(listBox1.SelectedIndex).ToString()
            testmc.LoadMediaFile()
            Dim n_height As Integer = testmc.Height, n_width As Integer = testmc.Width
            testmc.PlayLoop()
            Dim next_alias As Integer = 1
            Dim xpos As Integer = n_width , ypos As Integer = 0
            While ypos < panel1.Height
                While xpos < panel1.Width
                    Dim newmc As New MCIControl.MCIControl.MCIPlayerControl()
                    panel1.Controls.Add(newmc)
                    newmc.Location = New Point(xpos, ypos)
                    newmc.ShowControls = False
                    newmc.Size = New Size(n_width, n_height)
                    newmc.Visible = True
                    newmc.[Alias] = next_alias
                    newmc.Mediafilename = listBox1.Items(listBox1.SelectedIndex).ToString()
                    newmc.LoadMediaFile()
                    newmc.PlayLoop()
                    newmc.ToggleAudio()
                    next_alias += 1
                    xpos += (n_width )
                End While
                ypos += (n_height )
                xpos = 0
            End While

            panel1.Focus()
  

Panel1 is created at run time to occupy the complete client area of the maximized demo form. We then create the first MCIPlayerControl control, add it to the panel and set it at the top left hand corner. We want each control not to occcupy more than 1/3 of the width or the 1/3 of the height of the maximized form. The initial size of the control is set to this dimension: 1/3 form height and 1/3 form width. Before we load the file to the control, we set its ShowControls property to false. This is to inform MCIPlayerControl not to show its UI components (track bar, caption, buttons ..). With this property set to false MCIPlayerControl would be resized to fit the display of the video file, maintaining the aspect ratio and keeping to within the area of the initial size set. After the required area has been calculated, the excess area in the control is cut off, such that all the area of the control will be used for video display with no border margin.

Once we get the new size, we store it in n_width and n_height variable. We use the while loops to create all the other controls, so that each control will just touches the previous one. As each control is created and added to the panel, it is being played.

As the controls are played at sightly different time, each one would be sightly out of sync with the previous one. The time lapse between the displays may be in tens or hundreds millisec, depending on how fast your system loads and plays back the media files. 

Demo

The left listbox shows all the video files in the current directory. There are 2 MCIPlayerControl controls in the form.

The left one has no UI controls and the right one with UI controls.

To control the play of the left control, I have added in some buttons. You get the control with no UI controls by setting its ShowControls property to false. The advantage of not having UI controls is that you can set the size of the control (by using the mouse at design time, or setting the control's size property or width and height property at run time) and the display will be sized accordingly.

For the right control, you can click on the UI controls to manage the video play. The left bottom'buttons' are for "seek start", "pause/play","seek end", "slow motion" and  "fast forward". The right button is to toggle the audio on/off. You can also use the track bar to position to the frame that you want to resume playing from.

You will have to select a video file from the listbox to load into the control. You can load each control with the same file or different file. Then click the Load buttons. 

If you click the top right [ ] (round rectangle) 'button' for the right MCIPlayerControl control , you will get a pop up viewer window.  You can use the <space> bar to pause and resume play. Press 'P' to save a screen shot of the current frame. 

 

Press F11 to toggle showing the viewer in full screen and normal  window. If the viewer window is closed (using <Esc> key or the Close button), the display will be back into the  MCIPlayerControl control. 

In Version 2, I have enhanced the viewer to show/hide the various video controls, as shown in the picture above. If there is no mouse activity, the controls and the mouse cursor will auto-hide. Once you move the mouse, the mouse cursor and controls will appear again. 

To show the video wall, first select a video file from the listbox and then configure the following parameters, and finally click the Video Wall button

  • Video Lapse (ms): The time delay before starting the next video 
  • Min Num Across: Minimum number of video across the wall
  • Min Num Down: Minimun number of video down the wall
  • Sync Video: If this is set the Video Lapse parameter will be ignored. All video will be in sync
  • Random Overlap: Video will appear overlapped and entire wall will be covered with video of slightly uneven size, just as in the Top picture for this article

All the video that are currently loaded will be unloaded, the form would be maximized,and the selected video file will be played in an array of MCIPlayerControl controls. 

While the video wall is displayed, you can use the <space> bar to pause and resume play.  

To unload the video wall, press the Esc key.

 

Video Wall Video Synchronization

If we do not set the sync flag, we would have a delay after we have started playing a video, otherwise we seek to the start of the video (at 100ms position) and pause the video. By the time we are out of the for loop, if the sync flag is not set, then all the video has started playing each starting at a different time. Otherwise if the sync flag is set, all the video are in the pause state, we then emulate a <space> key calling the Form1_KeyPress event handler to resume play for all the paused video.

 for (i= panel1.Controls.Count -1; i>=0;i--)
 {
  ..
  ((MCIControl.MCIPlayerControl)c).PlayLoop();

  if (!sync)
  {
      System.Threading.Thread.Sleep(vScrollBarDelay.Value);
  }
  else 
  {
      ((MCIControl.MCIPlayerControl)c).SeekPlayPosition(100);
  }
}

  if (sync)
  {
      KeyPressEventArgs e = new KeyPressEventArgs(' ');
      Form1_KeyPress(null, e);
  }

 

  For i As Integer = panel1.Controls.Count - 1 To 0 Step -1
   ...
        DirectCast(c, MCIControl.MCIPlayerControl).PlayLoop()

		If Not sync Then
			System.Threading.Thread.Sleep(vScrollBarDelay.Value)
		Else
		  DirectCast(c, MCIControl.MCIPlayerControl).SeekPlayPosition(100)

		End If
  Next i 

  If sync Then
	  Dim e As New KeyPressEventArgs(" "C)
	  Form1_KeyPress(Nothing, e)
  End If
				

 

Video Viewer Window with Overlay

With mciSendString command, we can use the window command:

  Pcommand = "window " + _alias + " handle " + handle;
  _MCISendString(Pcommand);
  Pcommand = "window " & _alias & " handle " & handle
  _MCISendString(Pcommand)

This is how we are able to direct the video display to any window handlers. For popup viewing, we have redirect the video display to the FormViewer form. Once we have done that, the MCI system will manage the rendering of the video into this window. We would not be able to show UI controls in the FormViever form as it gets constantly updated and any UI controls placed in FormViewer form will not be displayed constantly. To solve this problem, we create a Transparent form FormOverlay and we position it over the client area of FormViewer. We then place all our UI controls on this transparent form. 

To create a Transparent form, we set the background color of the form to the same value as the TransparencyKey property. In our case we set use the color White. 

For this implemenation to work, we have to ensure:

  • FormOverlay is always in front of FormViewer in the z-order
  • FormOverlay position is same as the position of FormViewer client area
  • FormOverlay size is the same as size of FormViewer client area

For the requirements above, we handle the events for FormViewer's Resize, LocationChanged and Activate.

        private void FormViewer_LocationChanged(object sender, EventArgs e)
        {
            if (!_frmOverlay.Visible) return;
            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
            _frmOverlay.Location = new Point(this.Left + rect.Left, this.Top + rect.Top);

        }

        private void FormViewer_Activated(object sender, EventArgs e)
        {
            _frmOverlay.Visible = true;
            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
            _frmOverlay.Location = new Point(this.Left +rect.Left, this.Top +rect.Top);
            _frmOverlay.Size = new Size(rect.Width, rect.Height);

            this.BringToFront();
            _frmOverlay.BringToFront();
             this.Focus();

        }

        private void FormViewer_Resize(object sender, EventArgs e)
        {

           ....
            if (!_frmOverlay.Visible) return;
            Rectangle rect = ScreenCapture.GetClientRect(this.Handle);
            _frmOverlay.Size = new Size(rect.Width, rect.Height);
        }
        Private Sub FormViewer_LocationChanged(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.LocationChanged
            If Not _frmOverlay.Visible Then
                Return
            End If
            Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
            _frmOverlay.Location = New Point(Me.Left + rect.Left, Me.Top + rect.Top)

        End Sub

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

            _frmOverlay.Visible = True
            Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
            _frmOverlay.Location = New Point(Me.Left + rect.Left, Me.Top + rect.Top)
            _frmOverlay.Size = New Size(rect.Width, rect.Height)

            Me.BringToFront()
            _frmOverlay.BringToFront()
            Me.Focus()

        End Sub  

        Private Sub FormViewer_Resize(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Resize
           ...
            If Not _frmOverlay.Visible Then
                Return
            End If
            Dim rect As Rectangle = ScreenCapture.GetClientRect(Me.Handle)
            _frmOverlay.Size = New Size(rect.Width, rect.Height)
        End Sub

 

Note that since FormOverlay is now always in front of FormViewer, it would be getting the keyboard events. For mouse events, whatever area in FormOverlay that is color White (the TransparencyKey color), it would not get the Windows mouse messages and these messages would pass through to the window below, in our case FormViewer.

Taking note of this behaviors, we handle keyboard events at FormOverlay and call the FormViever keyboard events handlers.

        private void FormOverlay_KeyUp(object sender, KeyEventArgs e)
        {
            _parent.FormViewer_KeyUp(null, e);
           
        }

        private void FormOverlay_KeyPress(object sender, KeyPressEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("FormOverlay kp");
            _parent.FormViewer_KeyPress(null, e);
           
        }
        Private Sub FormOverlay_KeyUp(ByVal sender As Object, ByVal e As KeyEventArgs) Handles MyBase.KeyUp
            _parent.FormViewer_KeyUp(Nothing, e)

        End Sub

        Private Sub FormOverlay_KeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs) Handles MyBase.KeyPress
            _parent.FormViewer_KeyPress(Nothing, e)

        End Sub

 

As FormViewer is able to get the passed through mosue events, we use its MouseMove event to detect mouse activities to allow us to know when we should show the UI controls. Once we move the mouse over the video display, the UI controls will appear.

        private void FormViewer_MouseMove(object sender, MouseEventArgs e)
        {
            
            if (Math.Abs(_mx - e.X) >= 1 || (Math.Abs(_my - e.Y)) >= 1)
            {
                _mm++;
              //  System.Diagnostics.Debug.WriteLine(_mx + "," + _my + " - " + e.X + "," + e.Y);
                _frmOverlay._controlpanelbusy = true;           

            }
            _mx = e.X; _my = e.Y;

            if (_mm > 10)
            {
                _mm = 0;
                if(_frmOverlay !=null)
                   _frmOverlay.panel1.Visible = true;
               
            }
            
        }
        Private Sub FormViewer_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseMove

            If Math.Abs(_mx - e.X) >= 1 OrElse (Math.Abs(_my - e.Y)) >= 1 Then
                _mm += 1
                '  System.Diagnostics.Debug.WriteLine(_mx + "," + _my + " - " + e.X + "," + e.Y);

                _frmOverlay._controlpanelbusy = True
            End If
            _mx = e.X
            _my = e.Y

            If _mm > 10 Then
                _mm = 0
                If _frmOverlay IsNot Nothing Then
                    _frmOverlay.panel1.Visible = True

                End If
            End If

        End Sub

These UI controls, although on FormOverlay, as they are not colored White (the TransparencyKey color), they would be able to receive mouse events. We are thus able to click on them. We keep a timer in FormOverlay to fire every 5 seconds. If we clicked on any UI controls, we set a _controlpanelbusy flag. When the timer fires, it checks for this flag. If set, it un-sets it and returns, otherwise, it would set the UI controls panel visibility to false.

        private void timer1_Tick(object sender, EventArgs e)
        {
         
            if (_controlpanelbusy)
            {
                _controlpanelbusy = false;
                return;
            }
            panel1.Visible = false;          
                       
        }
		Private Sub timer1_Tick(sender As Object, e As EventArgs)

			If _controlpanelbusy Then
				_controlpanelbusy = False
				Return
			End If
			panel1.Visible = False

		End Sub

 

Points of Interest

The MCIPlayerControl is quite a light weight control, compared to Windows Media Player. On a fast system, loading a wall of controls (about 12 - 16) should take up less than 50% of the CPU processing.

Maybe you can think of some more interesting use for the control besides those already mentioned here. 

It is very easy to use the   MCIPlayerControl  in your Window Form projects, just drag it into your form and at runtime use the method:

LoadMediaFile(string filename, int alias,bool resize)

to load the filename with a unique numeric alias and resize as false to get a video player with full UI controls.

Have fun playing the MCIPlayerControl. 

History

18 June 2014: MCIPlayerControl V1

19 June 2014: MCIPayerControl V1a:

Pop up Viewer window and Video wall in full screen mode

25 Jun 2014: MCIPlayerControl V1b

Add in configuration settings for the Video wall

27 Jun 2014: MCIPlayerControl V2

Add in Overlay to Viewer with controls that show/hide based on mouse activity just like Windows Media Player

28 Jun 2014:

Add in more content in article with details on Video wall synchronization and  FormOverlay implementation

License

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

Share

About the Author

Yang Kok Wah
Software Developer (Senior)
Singapore Singapore
Yang Kok Wah is a Software Specialist in a leading System/Solution Integration Company in Singapore. He has more than 20 years experience in software development, specializing in areas of Biometrics, Smartcards and Image Processing. He has worked with VB, C#, Java and C/C++. He graduated from University of London with BSc(Hons) in Computing and also holds a Business Administration degree from National University of Singapore.
 
In his free time, he writes computer programs as a form of relaxation. He likes Graphics, Games, AI and Image Processing.

Comments and Discussions

 
GeneralMy vote of 5 PinpremiumVolynsky Alex28-Jun-14 9:22 

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

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

| Advertise | Privacy | Mobile
Web01 | 2.8.140827.1 | Last Updated 1 Jul 2014
Article Copyright 2014 by Yang Kok Wah
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid