Introduction
With DirectX 9, it's finally possible to use DirectX from C# without having to use interop. This includes the AudioVideoPlayback
namespace, which as the name would imply, is used to play audio and video files. It comprises largely of two objects, an Audio
class, and a Video
class (which contains an Audio
property). The interface looks pretty simple, and so you'd be forgiven for quickly deciding that writing a video player would be as easy as this:
if (browseForFile)
{
if (video == null)
video = new Video(filePath);
else
video.Open(filePath);
video.Owner = videoPanel;
video.Size = videoPanel.Size;
video.Ending +=new EventHandler(video_Ending);
video.Play();
}
Unfortunately, almost every line of this code is broken. The point of this article is to explain the pitfalls of the AudioVideoPlayback
in DirectX 9, and the ways that I found to work around them. I'll do this line by line, turning the above example into something that reliably works.
The constructor
The first one is the worst one, in a lot of ways. The Video
class offers an Open
method, it does not offer a parameterless constructor. In other words, you must build a Video
object out of a file, and then you have a method whereby you can open other videos with the same object. It leaks memory. Like crazy. In order to reuse a Video
object, you need to first delete it, like this:
if (video != null)
{
video.Stop();
video.Dispose();
video = null;
}
video = new Video(filePath);
Yes, you need to force destruction of the Video
object, and create a new one. I have no idea why, or how this can be adequately explained (especially given the great fuss that Microsoft made over code now being 'managed'). I certainly urge you not to ever use the Open
method.
The owner and the size
Setting the owner of the video is fine, not too much can go wrong, so long as you get it right. I got some really weird behavior during development, but nothing that didn't go away when I sorted out my code. The size is a different story. You can set the size of a video and it will play in that size fine. But once the video ends, the control it is playing on will resize itself to the native resolution of the video. To defeat this, you need to set an OnSize
event handler in the control that the video plays on, and manually resize the control to the desired size, which can be stored as a property or calculated for a resizable window, or simply write code that stops a window from ever shrinking if your app is topmost and full screen (so the screen res is never changed, and the app is never resized). In the case of the sample app, it looks like this:
private void videoPanel_SizeChanged(object sender, EventArgs e)
{
videoPanel.Size.Width = this.Size.Width - 10;
videoPanel.Size.Height = this.Size.Height - 10;
}
Catching the end of play
When I saw that the Video
object has an event handler for the end of a file, I thought that seemed pretty logical and useful. It is, and sometimes, it even gets called. The Audio
object event always seems to fire, unless it's the Audio
property on a Video
object, but video just does not consistently work. So I set up a separate timer, to try and work out for myself what was going on, something like:
private void videoTimer(object sender, EventArgs e)
{
if (!video.Playing)
OnVideoEnd(sender, e);
}
Unfortunately, even when a video stops, the Playing
property remains 'true
'. So instead, I had to do this:
private void videoTimer(object sender, EventArgs e)
{
if (video.CurrentPosition >= video.Duration)
OnVideoEnd(sender, e);
}
The CurrentPosition
always seems to go past the Duration
, even though the Playing
property remains true
, and the Ending
event often does not fire.
How loud is my audio?
The Audio
property of a Video
contains all the same methods as the Audio
class, but they work on the audio contained in a video clip. For my application, I needed to play the clip on the second monitor, and provide a preview on the main monitor. So I decided to play the video twice, and turn the sound off on one of them. The Audio
class has a Volume
property, an integer. I tried setting this to 0, and nothing seemed to happen. I read some docs, which said that off was 10000, and on is 0, so I tried that and it blew up. Finally, I found another snippet in the MSDN which told me (correctly) that off is -10000. I'm sure there is some esoteric reason why the property is called Volume
, and 0 is full on, and -10000 (why not -100000, or -94567547???) is off. Nevertheless, that's how it works.
More than video?
If you want to use a panel to display still images and video, this may work, so long as you destroy the video object first. I had no luck though, the last frame of video always stayed, and I could not draw over it. If you create a custom control which encapsulates bringing forward a Panel
for video, or a PictureBox
for stills, that won't work either. Putting a PictureBox
and a Panel
on your main form and calling BringToFront
on whichever one you are using works fine though.
Two videos at once?
My next problem was the showstopper. My application runs in two screens, with one screen showing full screen video, and the other showing a thumbnail. The only way I could see to do this was to play two videos. The screen with a thumbnail also has a custom control which shows a list of available media. As soon as this control is used, the main video screen would freeze. A bit of experimentation established that no matter what size your two video objects were, the biggest one freezes when there are paint messages about. The client was not happy.....
A wrapper class?
I fully intended to provide a wrapper class that turns the Video
object into something that behaves in a rational manner, so people could use that instead, but then I heard from someone else that they were having trouble playing 320 kbps MP3s, and getting the same callback problems with audio that I had with video. I have tested mostly on video, given that it seems a superset of audio in terms of what it does, so with a week to go before delivery, I gutted my project and replaced DX9 with the Windows Media Player control. Yes, the code is not as pretty, but guess what? Once I worked out the messages it sends me (and sends me every time, may I add), I suddenly found that everything worked for the first time. Not only that, but I can play two big videos with no problems at all. The only trick is to stop the players from autostarting, so both can load their copy of the video before asking both to play it. Unless you have a very specific reason to use DirectX9, I totally recommend you forget it, and play audio and video in C# using the Media Player control. The sample in the SDK gives you everything you need to work it all out, but if a lot of people ask for a tutorial, I'll write one.
Where from here?
The first thing I did when taking on this job was order a book from Amazon on Managed DirectX 9. Do not buy this book if you want info on playing video and audio. You've got a lot more info in this article than the book provides. There's only one bit of info I have not covered that the book explains, and it's this. The AudioVideoPlayback
functionally in the C# portion of DirectX 9 is not meant to be complete, but just enough to allow simple playback of video files. There are no plans to update/improve/add to this functionality in Managed DirectX. Enough said, in my opinion. I'm just not sure why they bothered.