Microsoft DirectX 8.1 (C++) |
This section describes how to implement a seek bar for a media-player application. The seek bar is implemented as a trackbar control.
When the application starts, initialize the trackbar:
HWND hTrack = GetDlgItem(hwnd, IDC_SLIDER1);
EnableWindow(hTrack, FALSE);
SendMessage(hTrack, TBM_SETRANGE, TRUE, MAKELONG(0, 100));
The trackbar is disabled until the user opens a media file. The trackbar range is from 0 to 100. The application converts from media time to a percentage of the file duration. For example, trackbar position "50" always corresponds to the middle of the file. (An alternate approach would be setting the trackbar's maximum range to the duration of the file. However, media time is a 64-bit integer, and trackbar ranges are limited to 32 bits.)
When the user opens a file, the application builds a file-playback graph. It queries the filter graph manager for the IMediaSeeking interface (not shown). The application then checks whether the graph can seek and whether it can retrieve the file duration. If the graph supports both of these capabilities, the application retrieves the duration and enables the trackbar:
REFERENCE_TIME g_rtTotalTime;
DWORD caps = AM_SEEKING_CanSeekAbsolute | AM_SEEKING_CanGetDuration;
if (pSeek && S_OK == pSeek->CheckCapabilities(&caps))
{
pSeek->GetDuration(&g_rtTotalTime);
EnableWindow(hTrack, TRUE);
}
When the user issues a "Play" command, the application runs the graph. It also starts a timer event that periodically updates the trackbar position. As each timer event is activated, the timer callback retrieves the current playback position and sets the trackbar accordingly:
#define TICKLEN 100 // Tick length (frequency of trackbar update).
MMRESULT wTimerID;
void CALLBACK MediaTimer(UINT wTimerID, UINT msg,
DWORD dwUser, DWORD dw1, DWORD dw2)
{
REFERENCE_TIME rtNow;
pSeek->GetCurrentPosition(&rtNow);
long sliderTick = (long)((rtNow * 100) / g_rtTotalTime);
SendMessage( hScroll, TBM_SETPOS, TRUE, sliderTick );
}
void OnUserPlay()
{
pGraph->Run();
if(wTimerID) // Is timer event pending?
{
timeKillEvent(wTimerID); // Cancel the timer.
wTimerID = 0;
}
wTimerID = timeSetEvent(TICKLEN, 50, MediaTimer, NULL, TIME_PERIODIC);
}
When the user stops or pauses the graph, the application cancels the timer event.
When the user drags or clicks the trackbar control, the application receives a WM_HSCROLL event. The low word of the wParam parameter is the trackbar notification message. The relevant messages are TB_ENDTRACK, which is sent at the end of the trackbar action, and TB_THUMBTRACK, which is sent continuously while the user drags the trackbar.
The following function handles both messages:
void HandleTrackbar(HWND hTrackbar, short int userReq)
{
// If the file is not seekable, the trackbar is disabled.
// Therefore, seeking is valid here.
static OAFilterState state;
static BOOL bStartOfScroll = TRUE;
if (userReq == TB_ENDTRACK || userReq == TB_THUMBTRACK)
{
DWORD Position = SendMessage(hTrackbar, TBM_GETPOS, 0, 0);
// Pause when the scroll action begins.
if (bStartOfScroll)
{
pControl->GetState(10, &state);
bStartOfScroll = FALSE;
pControl->Pause();
}
// Update the position continuously.
REFERENCE_TIME newTime = (g_rtTotalTime * Position) / 100;
pSeek->SetPositions(&newTime, AM_SEEKING_AbsolutePositioning,
NULL, AM_SEEKING_NoPositioning);
// Restore the state at the end.
if (userReq == TB_ENDTRACK)
{
if (state == State_Stopped)
pControl->Stop();
else if (state == State_Running)
pControl->Run();
bStartOfScroll = TRUE;
}
}
}
In order to seek smoothly while the user drags the trackbar, the function pauses the graph until the user releases the trackbar. If the graph was running, paused mode prevents the graph from continuing to play while the user is dragging the control. If the graph was stopped, paused mode ensures that the video window is updated. On receiving the TB_ENDTRACK message, the function restores the graph to its original state.