Writing a DVD Playback Application in DirectShow

 

September 2004

 

Applies to:

   Microsoft DirectShow®

Download the code sample.

Summary: Describes how to write a DVD playback application in C++ using DirectShow. Readers should be familiar with DirectShow programming.

 

Contents

Introduction

Overview of DVD Video

   Titles and Chapters

   Subpicture Streams

   Menus and Buttons

   Domains

Building the DVD Playback Graph

Configuring the Video Renderer

   Windowed Mode

   Windowless Mode

DVD Commands

Menu Navigation

Synchronizing DVD Commands

   No Synchronization

   Blocking

   Synchronization Object

   Command Status Events

   Flushing the DVD Navigator's Buffers

Data Flow

   Stopping Playback

   Pausing Playback

Identifying Valid Operations

For More Information

Introduction

This article describes how to write a DVD playback application in C++ using Microsoft® DirectShow®, and includes a sample application to demonstrate the programming tasks described here. The article starts with an overview of some technical details about DVD video, then describes the main tasks for a DVD application: menu navigation, playback commands, and command synchronization.

Overview of DVD Video

You don't need to be an expert in DVD to write a DVD playback application, but a quick overview of DVD technology may be helpful.

Titles and Chapters

The video content in a DVD is divided into titles and menus. Titles are further divided into units that the DVD specification calls parts of titles (PTTs). More often, these are called scenes or chapters. (The DirectShow documentation uses the term chapter.) The viewer can navigate to specific titles or chapters within titles.

The author of a DVD decides how to divide the content into titles and chapters. When a DVD contains a feature-length film, the entire film is often placed in one title, divided into chapters for the individual scenes. Extra features on the DVD, such as trailers or deleted scenes, are placed in separate titles. However, these divisions are arbitrary, and many DVDs are organized differently.

Subpicture Streams

A DVD may contain up to 32 subpicture streams. These consist of compressed 16-color bitmaps with an alpha channel, which are overlayed on top of the video. Typically, subpicture streams contain subtitles and menu buttons, although they may contain other graphics as well. A subpicture stream may have a specified language. Some subpicture content is always shown, and some subpicture content is shown only if the user enables it.

Note that captions in a subpicture stream are not the same as line-21 closed captions. Closed captions, which are intended for hard-of-hearing viewers, are embedded in the video signal. They consist entirely of character strings. Subpicture captions, on the other hand, are graphical bitmaps. On a consumer device, closed captions are displayed by the television set, while the subpicture stream is rendered by the DVD player. A DVD may contain both types of caption.

The DVD specification defines six types of menu:

  • Title. The title menu is the first menu to be displayed. Usually it has buttons for selecting titles. The title menu is also called the video manager menu. There is only one title menu on a DVD.
  • Root. A root menu is the top-level menu for a title. Each title can have a root menu. The next four menus are submenus from the root menu.
  • Subpicture. The subpicture menu selects the subpicture stream.
  • Audio. The audio menu selects the audio stream. Typically, this menu enables the viewer to select a language track.
  • Angle. The angle menu selects the camera angle.
  • Chapter. The chapter menu, also called the PTT menu, selects chapters within a title.

Most menus have buttons, which can be selected and activated. Selecting a button changes the button's appearance. Activating a button triggers a DVD command, such as showing another menu or starting playback.

Figure 1 shows how the menus might be organized on a typical DVD. In this example, the title menu has buttons for each title. These navigate to the root menus for each title. The root menu for Title 1 has a button to start playback and buttons for two submenus, a chapter menu and an audio menu.

Image: DVD menu example

Figure 1. Example menu structure.

This diagram shows just one possibility; there is wide variation in the way DVD menus are organized. On some DVDs, the title menu and the root menu are the same. On others, the title menu has no buttons, but simply plays an introductory video, after which the DVD jumps directly to the root menu. The functionality of a submenu may not match the name of the menu. For example, a "chapter" menu might have buttons to select the audio stream. Also, buttons are not restricted to menus; they can also appear within the video title. For these reasons, the user interface (UI) in your application should be as generic as possible.

A player application should respond to mouse messages when the DVD Navigator displays a menu. When the mouse hovers over a button, select the button. When the mouse clicks on a button, activate the button. You might also provide directional controls (left, right, up, and down) to change the button selection, equivalent to the standard controls on a DVD player. Menu navigation is described in more detail in the topic Menu Navigation.

Domains

The term domain refers to the internal state of a DVD player; it is not something authored on the disc. Domains are important because some DVD commands are only valid in certain domains. DirectShow provides a way to query the current domain and to be notified when the domain changes. The following domains are defined:

  • First Play. In this domain, the DVD player has just started playing the DVD. After it enters the First Play domain, the player switches to another domain—either a menu domain or the title domain, depending on the disc.
  • Video Manager Menu. The player is showing the Video Manager Menu, also called the title menu.
  • VTS Menu. The player is showing a menu associated with a video title set, either the root menu or a submenu (audio, sub-picture, angle, or chapter).
  • Title. The player is playing video in a title.
  • Stop. The player is not displaying anything. (Strictly speaking, the DVD specification does not call this state a domain, but it can be treated as one.)

Building the DVD Playback Graph

As with any DirectShow application, a DVD playback application starts by building a filter graph. DirectShow provides the following components for DVD playback:

  • DVD Graph Builder. A helper object that constructs the filter graph. It exposes the IDvdGraphBuilder interface.
  • DVD Navigator. A DirectShow filter that handles DVD playback, navigation, and other commands.

DVD playback also requires an MPEG-2 decoder. Hardware and software MPEG-2 decoders are available from third parties. DirectShow does not provide an MPEG-2 decoder.

First, create an instance of the DVD Graph Builder object.

IDvdGraphBuilder *pBuild = NULL;
hr = CoCreateInstance(CLSID_DvdGraphBuilder, NULL, 
    CLSCTX_INPROC_SERVER, IID_IDvdGraphBuilder, (void **)&pBuild);

At this point, you can select and configure the video renderer before you build the rest of the graph. This step, which is optional, is described in more detail in the next section. If you omit this step, the DVD Graph Builder selects a default renderer. Next, build the graph by calling the IDvdGraphBuilder::RenderDvdVideoVolume method.

AM_DVD_RENDERSTATUS buildStatus;
hr = pBuild->RenderDvdVideoVolume(L"Z:\\video_ts", 0, &buildStatus);

The first parameter is the name of a directory that contains the DVD files. On a DVD disc, these files reside in a directory named VIDEO_TS. If the first parameter is NULL, the DVD Graph Builder uses the first drive that contains a DVD volume.

The second parameter contains various optional flags for choosing the type of decoder (hardware or software) and other options.

The third parameter is an AM_DVD_RENDERSTATUS structure that receives status information. If the RenderDvdVideoVolume method returns S_FALSE, it means the call partially succeeded (or partially failed, if you're a pessimist). For example, the method might fail to render the subpicture stream, even though the other streams rendered successfully. If the RenderDvdVideoVoume method returns an error code or the value S_FALSE, you can examine the AM_DVD_RENDERSTATUS structure for details about the error.

Next, get a pointer to the Filter Graph Manager by calling IDvdGraphBuilder::GetFiltergraph. This method returns a pointer to the Filter Graph Manager's IGraphBuilder interface.

IGraphBuilder *pGraph = NULL;
hr =  pBuild->GetFiltergraph(&m_pGraph);

Use the IDvdGraphBuilder::GetDvdInterface method to retrieve DVD-related interfaces, including the following:

  • IDvdControl2. Controls playback and DVD commands
  • IDvdInfo2. Returns information about the DVD Navigator's current state.
  • IAMLine21Decoder. Controls closed caption display. Closed caption display is enabled by default. To disable it, call IAMLine21Decoder::SetServiceState with the AM_L21_CCSTATE_Off flag.
  • IBasicAudio. Controls audio volume and balance.

For example, the following code returns the IDvdControl2 interface.

IDvdControl2 *pDvdControl = NULL;
hr = pBuild->GetDvdInterface(IID_IDvdControl2, (void**)&pDvdControl);

For more detailed code examples, see the CDVDGraph::Initialize and CDVDGraph::ShowDVD methods in the sample application.

Configuring the Video Renderer

DirectShow provides several video renderer filters. Before you build the graph, you can chose which video renderer you prefer. Select the renderer by calling IDvdGraphBuilder::GetDvdInterface and requesting an interface that is specific to that renderer:

  • Overlay Mixer Filter: IDDrawExclModeVideo.
  • Video Mixing Renderer 7 (VMR-7): IVMRFilterConfig.
  • Video Mixing Renderer 9 (VMR-9): IVMRFilterConfig9.

If you request any of these interfaces before building the filter graph, the DVD Graph Builder creates the corresponding video renderer. Later, when you build the graph, the DVD Graph Builder will try to use that renderer. But if it cannot build the graph using the renderer you selected, it may switch to another renderer. For example, your MPEG-2 decoder might not be compatible with the VMR filter, in which case the DVD Graph Builder would default to the Overlay Mixer.

These interfaces also give you a chance to configure the renderer before it is connected to the decoder. For example, you can set the VMR to use windowless mode instead of the default windowed mode. For more information about video renderers, see the topic About Video Rendering in DirectShow in the SDK documentation.

Windowed Mode

In windowed mode (Overlay Mixer or VMR), the renderer creates its own video window. To make this window a child of the application window, call IVideoWindow::put_Owner with a handle to the application. Also call IVideoWindow::put_WindowStyle to set the WS_CHILD and WS_CLIPSIBLINGS styles on the renderer's video window. To get mouse messages from the renderer's video window, call IVideoWindow::put_MessageDrain with a handle to the application window. This method sets up a "message drain" — the video window forwards any mouse messages it receives to the message drain window.

pVideoWindow->put_Owner((OAHWND)hwnd);
pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
pVideoWindow->put_MessageDrain((OAHWND)hwnd) ;

The message drain does not have to be the direct parent of the video window. Figure 2 shows an example where the video window's parent is a child of the application window, but the application window is the message drain.

Image: Video window message drain

Figure 2. Example menu structure.

Unfortunately, the message drain makes selecting DVD menu buttons somewhat complicated. Assuming the video window does not fill the application's entire client area, some mouse events will fall outside the video window. When you get a mouse event from inside the video window, you should process it for DVD menu navigation. Mouse events from outside the video window should not be processed. With the message drain, there is no way to distinguish between the two. Furthermore, the coordinates for mouse events from the video window are relative to the video window's client area; but mouse events from outside the video window are relative to the application's client area.

The sample application solves this problem by subclassing the parent of the video window. When the video window forwards mouse messages to the control, the control redirects them to the application using a private window message named WM_PANEL. This private message enables the application to distinguish forwarded messages from those received directly by the application window. The subclassed control is defined in the header file Panel.h.

Image: Subclassed control

Figure 3. Subclassed control.

Windowless Mode

Windowless mode avoids the problems with mouse messages altogether. You do not need a message drain, because the VMR does not create its own window in windowless mode. Instead, it draws directly onto your application window. If the destination rectangle is smaller than the application client area, the DVD Navigator takes this into account when it calculates the DVD button positions. Therefore, when you get a mouse message, you can pass the coordinates directly to the DVD Navigator, as described in the section Menu Navigation.

The sample application defines a class named CVideoManager that manages the video renderer. Before the graph is built, the CVideoManager class configures the VMR-7 for windowless mode. After the graph is built, it queries for the IVMRWindowlessControl interface. If that interface is not available, it falls back to using IVideoWindow. To some extent, this design isolates the application from the differences between the two rendering modes.

DVD Commands

The DVD navigation and playback commands are defined in a section of the DVD specification named Annex J, which is why the DirectShow documentation often refers to "Annex J commands." The names given in Annex J are not always very intuitive, however, so DirectShow uses names that may be easier to understand. Table 1 lists the Annex J commands and their DirectShow equivalents.

Table 1. DVD Commands

Annex J command Description IDvdControl2 method
Title_Play Play a title. PlayTitle
PTT_Play Play a chapter in a title. PlayChapterInTitle
Time_Play Play a title starting from a specified time. PlayAtTimeInTitle
Stop Stop playback. Stop
GoUp Return from a submenu to the parent menu. ReturnFromSubmenu
Time_Search Play at a specified time within the current title. PlayAtTime
PTT_Search Play a chapter within the current title. PlayChapter
PrevPG_Search Go to the start of the previous chapter and resume playback. PlayPrevChapter
TopPG_Search Go to the start of the current chapter and resume playback. ReplayChapter
NextPG_Search Go to the start of the next chapter and resume playback. PlayNextChapter
Forward_Scan Play forward at a specified playback rate. The default playback rate is 1.0. PlayForwards
Backward_Scan Play backward at a specified playback rate. PlayBackwards
Menu_Call Show a menu. ShowMenu
Resume Return from a menu and resume playback. Resume
Upper_Button_Select, Lower_Button_Select, Left_Button_Select, Right_Button_Select Select a button whose position is relative to the currently selected button. SelectButton
Button_Activate Activate the selected button. ActivateButton
Button_Select_and_Activate Select and activate a button. SelectAndActivateButton
Still_Off Resume playback when displaying a still image. StillOff
Pause_On Pause playback. Pause
Pause_Off Resume playback from the paused state. Pause
Menu_Language_Select Select the language for menus. SelectDefaultMenuLanguage
Audio_Stream_Change Set the audio stream. SelectAudioStream
Sub-picture_Stream_Change Set the subpicture stream; enable or disable subpicture display. SelectSubpictureStream
Angle_Change Set the camera angle. SelectAngle
Parental_Level_Select Set the parental level. SelectParentalLevel
Parental_Country_Select Set the country/region for parental management. SelectParentalCountry
Karaoke_Audio_Presentation_Mode_Change Set the audio mixing mode for karaoke. SelectKaraokeAudioPresentationMode
Video_Presentation_Mode_Change Set the aspect ratio mode to widescreen, letterbox, or pan scan. SelectVideoModePreference

The DVD Navigator might show a menu when the user activates a button, or when the Navigator enters the First Play domain. To show a menu programmatically, call the IDvdControl2::ShowMenu method.

There are several ways to select menu buttons programmatically:

  • To select a button by number, call IDvdControl2::SelectButton. Buttons are numbered 1 to 36. The IDvdInfo2::GetCurrentButton method returns the number of available buttons.
  • To select a button relative to the position of the currently selected button, call IDvdControl2::SelectRelativeButton. You can select a button in the up, down, left, or right direction.
  • To select a button by its coordinates within the window, call IDvdControl2::SelectAtPosition. This method takes (x,y) coordinates relative to the client area of the video window. (For windowless mode, this is the application window.) If there is no button at that location, the method returns VFW_E_DVD_NO_BUTTON.

In addition, there are several ways to activate a button:

  • To activate a button by number, call IDvdControl2::SelectAndActivateButton.
  • To activate a button by its coordinates, call IDvdControl2::ActivateAtPosition.
  • To activate the button that is currently selected, call IDvdControl2::ActivateButton. If no button is selected, the method returns VFW_E_DVD_NO_BUTTON.

Synchronizing DVD Commands

DVD commands do not always complete instantly. For this reason, some of the methods in IDvdControl2 are asynchronous. These include playback methods, such as PlayTitle, and menu navigation methods, such as ShowMenu and ReturnFromSubmenu. An asynchronous method returns immediately, without waiting for the command to complete. After the method returns, other events may prevent the command from completing, even if the method succeeded. DirectShow provides several options for synchronizing commands, ranging from no synchronization to full synchronization using filter graph events.

All of the asynchronous methods have a dwFlags parameter and a ppCmd parameter. The dwFlags parameter specifies the synchronization behavior, and the ppCmd parameter returns a pointer to an optional synchronization object. Different behaviors result depending on what values you give for these parameters.

No Synchronization

For a basic DVD playback application, the best option may be simply to ignore synchronization issues. Occasionally a command may fail or the UI might lag slightly when it updates, but these errors will be on the order of fractions of seconds.

To issue a command with no synchronization, set the DVD_CMD_FLAG_None flag in the dwFlags parameter and set the ppCmd parameter to NULL:

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, NULL);

Blocking

If you set the EC_DVD_CMD_FLAG_Block flag in the dwFlags parameter, the method blocks until the command completes:

hr = pDVDControl2->PlayTitle(uTitle, EC_DVD_CMD_FLAG_Block, NULL);

In effect, this flag turns an asynchronous method into a synchronous method. The drawback is that your UI blocks if you call the method from the application thread.

Synchronization Object

All of the asynchronous methods can return a synchronization object, which you can use to wait for the command to start or end. To get this object, pass the address of an IDvdCmd pointer in the ppCmd parameter:

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, &pCmdObj);

If the method succeeds, it returns a new IDvdCmd object. The IDvdCmd::WaitForStart method blocks until the command begins, and the IDvdCmd::WaitForEnd method blocks until the command ends. The return value indicates the status of the command.

The following code is functionally equivalent to setting the EC_DVD_CMD_FLAG_Block flag, shown previously.

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_None, &pCmdObj);
if (SUCCEEDED(hr))
{
    // Use pCmdObj to wait for the command to complete.
    hr = pCmdObj->WaitToEnd();
    pCmdObj->Release();
}

In this case, the PlayTitle method does not block, but the application blocks by calling WaitForEnd.

Command Status Events

If you set the DVD_CMD_FLAG_SendEvents flag in the dwFlags parameter, the DVD Navigator sends an EC_DVD_CMD_START event when the command begins and an EC_DVD_CMD_END event when the command ends.

The event's lParam2 parameter is the HRESULT return value for the command. The event's lParam1 parameter provides a way get the synchronization object for the command. If you pass lParam1 to the IDvdInfo2::GetCmdFromEvent method, the method returns a pointer to the synchronization object's IDvdCmd interface. You can use this interface to wait for completion of the command, as described earlier. However, if you passed NULL for the ppCmd parameter in the original IDvdControl2 method, the DVD Navigator does not create a synchronization object, and GetCmdFromEvent returns E_FAIL.

The following code shows how to use command status events with no synchronization object.

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_SendEvents, NULL);

// In your event handling code:
switch (lEvent)
{
   case EC_DVD_CMD_END:
       HRESULT hr2 = (HRESULT)lParam2;
       /* ... */ 
       break;
}

Note that without a synchronization object, you cannot tell which command is associated with the event. The following code shows how to use events with the synchronization object. The idea is to store the synchronization objects in a list and then compare object pointers when you get the  EC_DVD_CMD_START or EC_DVD_CMD_END event.

IDvdCmd *pCmdObj = NULL;
hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_SendEvents, &pCmdObj);
if (SUCCEEDED(hr)) 
{
    // Store pCmdObj in a list of pending commands.
}

// In your event handling code:
switch (lEvent)
{
case EC_DVD_CMD_END:
   {
       IDvdCmd *pObj = NULL;
       hr = pDvdInfo2->GetCmdFromEvent(lParam, &pObj);
       if (SUCCEEDED(hr)) 
       {
           // Find this object in your list by comparing IUnknown
           // pointers. Assume the following function is defined in 
           // your application:
           IDvdCmd *pPendingObj = GetPendingCommandFromList(pObj); 
           if (pPendingObj)
           {
               // Update UI accordingly (not shown). 
               pPendingObj->Release();
           }
           pObj->Release();
       }
    }
    break;
} 

Flushing the DVD Navigator's Buffers

During playback, the DVD Navigator buffers video data. The amount of buffered data varies. When the DVD Navigator switches to a new piece of video, data already in the pipeline is not lost, so the transition is seamless. By default, when the DVD Navigator issues a command, it does not flush data already in the pipeline. As a result, there may be some latency before you can see the effect of the command, depending on how much data is buffered. To increase the responsiveness, you can force the DVD Navigator to flush by setting the DVD_CMD_FLAG_Flush flag.

hr = pDVDControl2->PlayTitle(uTitle, DVD_CMD_FLAG_Flush, NULL);

This flag can be combined with any of the flags described previously, using a bitwise OR. A side effect of flushing is that some video may be lost, so do not use this flag if you need to guarantee there are no gaps in the video.

Data Flow

The DVD Navigator has methods to stop and pause playback. These methods are similar — but not identical — to the Stop and Pause methods in IMediaControl. Here is the difference between them:

  • The IDvdControl2 methods change what the DVD Navigator reads from the disk. They do not change the state of the graph.
  • The IMediaControl methods change the state of the graph. They do not change what the DVD Navigator reads from the disk. (There is one important exception, explained in the next section, related to the Stop method.)

For example, IDvdControl2::Pause method issues the Annex J "Pause_On" command, but does not pause the filter graph. The IMediaControl::Pause method, on the other hand, pauses the graph but does not issue any DVD command.

In general, use the IMediaControl::Pause and Stop methods instead of the corresponding IDvdControl2 methods. The IMediaControl methods have very small latencies, whereas the IDvdControl2 methods can have up to two seconds of latency.

Stopping Playback

The behavior of IMediaControl::Stop depends on a flag that you can set with the IDvdControl2::SetOption method.

  • If the DVD_ResetOnStop flag is FALSE, IMediaControl::Stop stops the graph, but does not change the DVD Navigator's domain. When you call run again, playback resumes from the current position.
  • If DVD_ResetOnStop is TRUE, IMediaControl::Stop causes the DVD Navigator to reset. When you call IMediaControl::Run again, the DVD Navigator plays from the First Play domain, as if you were inserting the DVD for the first time.

The DVD_ResetOnStop flag is true by default, for compatibility with older applications. Generally, however, you should override the default and set the flag to FALSE. The reason is that certain events can cause the graph to stop during playback. For example, if the display resolution changes, the filter graph stops, reconnects the video renderer, and restarts. If DVD_ResetOnStop is TRUE, playback will restart from the beginning of the disc. That is probably not what the user expects.

At the beginning of your application, therefore, call SetOption with DVD_ResetOnStop set to FALSE. If you want to stop playback and have it resume from the same location, call IMediaControl::Stop or IMediaControl::Pause. If you want to stop playback and reset the disk, call SetOption with DVD_ResetOnStop equal to TRUE; then call IMediaControl::Stop; finally, call SetOption again and reset DVD_ResetOnStop to FALSE.

Pausing Playback

If you give the DVD Navigator a command while the graph is paused, the command may not complete until the graph runs again. In some situations, this can cause a deadlock in your application. There are two rules you should follow to avoid deadlocks:

  • While paused, do not issue more than one asynchronous DVD command.
  • While paused, do not block the application's UI thread or the thread that changes the state of the graph.

The second rule is worth examining in more detail. Here are some specific scenarios that may cause a deadlock:

  • Scenario: While paused, the application issues a DVD command with the blocking flag. This can cause a deadlock if the thread that issues the DVD command is the same thread that issues the run command. The DVD command blocks until the graph runs, but the graph cannot run until the command completes.

    Recommendation: Issue the DVD command on a separate worker thread, or don't use the blocking flag.

  • Scenario: While paused, the application issues a DVD command, then calls IDvdCmd::Wait on the command object. This situation is equivalent to the previous example. If you call Wait from the UI thread, the UI thread cannot run the graph until the Wait method unblocks, but the Wait method will not unblock until the graph runs.

    Recommendation: Call Wait on a worker thread.

  • Scenario: While the graph is running, the application issues a DVD command with the blocking flag, and then calls pause from another thread. This is a possible race condition because the graph may pause before the command is issued. If one of the two threads is the UI thread, you may cause a deadlock similar to the previous two examples. This example illustrates the importance of writing thread-safe code if your application uses multiple threads.

    Recommendation: If you use worker threads, make sure your code is thread-safe.

  • Scenario: While paused, the application disables the run command from the UI, and then issues an asynchronous DVD command. This case is not strictly a deadlock, because the application thread is still running. However, the user is now prevented from running the graph, and therefore the command will never complete.

    Recommendation: When pausing, always leave the run command enabled.

Identifying Valid Operations

Several factors determine whether you can perform a given DVD operation:

  • The current domain. Some commands are only valid in certain domains. When the domain changes, the navigator sends an EC_DVD_DOMAIN_CHANGE event. You can also call IDvdInfo2::GetCurrentDomain to get the current domain.
  • UOPS flags. These are flags written onto the disc that indicate which operations are permitted. Whenever the flags change, the navigator sends a EC_DVD_VALID_UOPS_CHANGE event with the new flags. You can also call IDvdInfo2::GetCurrentUOPS to get the current UOPS flags.
  • DVD content. Some commands may not be relevant based on the content of the DVD. For example, the SelectAngle method might be permitted according to the current domain and UOPS flags, yet the video might have only one angle. In that case, the SelectAngle call is permitted but is not a meaningful option.

When in doubt, permit an action. At worst, the IDvdControl2 method will fail and you can give feedback to the user. The feedback should be relatively unobtrusive. For example, you might flash a small red X to alert the user. The DVD Navigator returns VFW_E_DVD_INVALIDDOMAIN when the domain prohibits an operation, and VFW_E_DVD_OPERATION_INHIBITED when the UOPS flags prohibit an operation.

The sample application includes a CDVDState class that has tests for every DVD function, based on the current domain and UOPS. The application uses these tests plus the current graph state to determine which UI controls should be enabled.

For More Information