2025-11-28 00:35:46 +09:00

713 lines
16 KiB
C++

//////////////////////////////////////////////////////////////////////////
// MainWindow.cpp: Main application window.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////
#include "DshowPlayer.h"
#include "MainWindow.h"
const LONG ONE_MSEC = 10000; // The number of 100-ns in 1 msec
const UINT_PTR IDT_TIMER1 = 1; // Timer ID
const UINT TICK_FREQ = 200; // Timer frequency in msec
// Forward declarations of functions included in this code module:
void NotifyError(HWND hwnd, TCHAR* sMessage, HRESULT hrStatus);
//-----------------------------------------------------------------------------
// MainWindow constructor.
//-----------------------------------------------------------------------------
MainWindow::MainWindow() : brush(NULL), m_timerID(0), m_pPlayer(NULL)
{
}
//-----------------------------------------------------------------------------
// MainWindow destructor.
//-----------------------------------------------------------------------------
MainWindow::~MainWindow()
{
if (brush)
{
DeleteObject(brush);
}
StopTimer();
SAFE_DELETE(m_pPlayer);
}
//-----------------------------------------------------------------------------
// MainWindow::OnReceiveMessage
// Description: Handles window messages
//-----------------------------------------------------------------------------
LRESULT MainWindow::OnReceiveMessage(UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
HRESULT hr;
switch (message)
{
case WM_CREATE:
hr = OnCreate();
if (FAILED(hr))
{
// Fail and quit.
NotifyError(m_hwnd, TEXT("Cannot initialize the application."), hr);
return -1;
}
break;
case WM_SIZE:
OnSize();
break;
case WM_PAINT:
OnPaint();
break;
case WM_MOVE:
OnPaint();
break;
case WM_DISPLAYCHANGE:
m_pPlayer->DisplayModeChanged();
break;
case WM_ERASEBKGND:
return 1;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_TIMER:
OnTimer();
break;
case WM_NOTIFY:
OnWmNotify((NMHDR*)lParam);
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch (wmId)
{
case IDM_EXIT:
DestroyWindow(m_hwnd);
break;
case ID_FILE_OPENFILE:
OnFileOpen();
break;
case IDC_BUTTON_PLAY:
OnPlay();
break;
case IDC_BUTTON_STOP:
OnStop();
break;
case IDC_BUTTON_PAUSE:
OnPause();
break;
case IDC_BUTTON_MUTE:
OnMute();
break;
}
break;
// Private filter graph message.
case WM_GRAPH_EVENT:
hr = m_pPlayer->HandleGraphEvent(this);
break;
default:
return BaseWindow::OnReceiveMessage(message, wParam, lParam);
}
return 0;
}
//-----------------------------------------------------------------------------
// MainWindow::OnCreate
// Description: Called when the window is created.
//-----------------------------------------------------------------------------
HRESULT MainWindow::OnCreate()
{
HRESULT hr = S_OK;
// Create the background brush.
brush = CreateHatchBrush(HS_BDIAGONAL, RGB(0, 0x80, 0xFF));
if (brush == NULL)
{
hr = __HRESULT_FROM_WIN32(GetLastError());
}
// Create the rebar control.
if (SUCCEEDED(hr))
{
hr = rebar.Create(m_hInstance, m_hwnd, IDC_REBAR_CONTROL);
}
// Create the toolbar control.
if (SUCCEEDED(hr))
{
hr = toolbar.Create(m_hInstance, m_hwnd, IDC_TOOLBAR, TBSTYLE_FLAT | TBSTYLE_TOOLTIPS);
}
// Set the image list for toolbar buttons (normal state).
if (SUCCEEDED(hr))
{
hr = toolbar.SetImageList(
Toolbar::Normal, // Image list for normal state
IDB_TOOLBAR_IMAGES_NORMAL, // Bitmap resource
Size(48, 48), // Size of each button
5, // Number of buttons
RGB(0xFF, 0x00, 0xFF) // Color mask
);
}
// Set the image list for toolbar buttons (disabled state).
if (SUCCEEDED(hr))
{
hr = toolbar.SetImageList(
Toolbar::Disabled, // Image list for normal state
IDB_TOOLBAR_IMAGES_DISABLED, // Bitmap resource
Size(48, 48), // Size of each button
5, // Number of buttons
RGB(0xFF, 0x00, 0xFF) // Color mask
);
}
// Add buttons to the toolbar.
if (SUCCEEDED(hr))
{
// Play
hr = toolbar.AddButton(Toolbar::Button(ID_IMAGE_PLAY, IDC_BUTTON_PLAY));
}
if (SUCCEEDED(hr))
{
// Stop
hr = toolbar.AddButton(Toolbar::Button(ID_IMAGE_STOP, IDC_BUTTON_STOP));
}
if (SUCCEEDED(hr))
{
// Pause
hr = toolbar.AddButton(Toolbar::Button(ID_IMAGE_PAUSE, IDC_BUTTON_PAUSE));
}
if (SUCCEEDED(hr))
{
// Mute
hr = toolbar.AddButton(Toolbar::Button(ID_IMAGE_MUTE_OFF, IDC_BUTTON_MUTE));
}
// Add the toolbar to the rebar control.
if (SUCCEEDED(hr))
{
hr = rebar.AddBand(toolbar.Window(), 0);
}
//// Create the slider for seeking.
if (SUCCEEDED(hr))
{
hr = Slider_Init(); // Initialize the Slider control.
}
if (SUCCEEDED(hr))
{
hr = seekbar.Create(m_hwnd, Rect(0, 0, 300, 16), IDC_SEEKBAR);
}
if (SUCCEEDED(hr))
{
hr = seekbar.SetThumbBitmap(IDB_SLIDER_THUMB);
seekbar.SetBackground(CreateSolidBrush(RGB(239, 239, 231)));
seekbar.Enable(FALSE);
}
if (SUCCEEDED(hr))
{
hr = rebar.AddBand(seekbar.Window(), 1);
}
//// Create the slider for changing the volume.
if (SUCCEEDED(hr))
{
hr = volumeSlider.Create(m_hwnd, Rect(0, 0, 100, 32), IDC_VOLUME);
}
if (SUCCEEDED(hr))
{
hr = volumeSlider.SetThumbBitmap(IDB_SLIDER_VOLUME);
volumeSlider.SetBackground(CreateSolidBrush(RGB(239, 239, 231)));
volumeSlider.Enable(TRUE);
// Set the range of the volume slider. In my experience, only the top half of the
// range is audible.
volumeSlider.SetRange(MIN_VOLUME / 2, MAX_VOLUME);
volumeSlider.SetPosition(MAX_VOLUME);
}
if (SUCCEEDED(hr))
{
hr = rebar.AddBand(volumeSlider.Window(), 2);
}
// Create the DirectShow player object.
if (SUCCEEDED(hr))
{
m_pPlayer = new DShowPlayer(m_hwnd);
if (m_pPlayer == NULL)
{
hr = E_OUTOFMEMORY;
}
}
// Set the event notification window.
if (SUCCEEDED(hr))
{
hr = m_pPlayer->SetEventWindow(m_hwnd, WM_GRAPH_EVENT);
}
// Set default UI state.
if (SUCCEEDED(hr))
{
UpdateUI();
}
return hr;
}
//-----------------------------------------------------------------------------
// MainWindow::OnPaint
// Description: Called when the window should be painted.
//-----------------------------------------------------------------------------
void MainWindow::OnPaint()
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(m_hwnd, &ps);
if (m_pPlayer->State() != STATE_CLOSED && m_pPlayer->HasVideo())
{
// The player has video, so ask the player to repaint.
m_pPlayer->Repaint(hdc);
}
else
{
// The player does not have video. Fill in our client region, not
// including the area for the toolbar.
RECT rcClient;
RECT rcToolbar;
GetClientRect(m_hwnd, &rcClient);
GetClientRect(rebar.Window(), &rcToolbar);
HRGN hRgn1 = CreateRectRgnIndirect(&rcClient);
HRGN hRgn2 = CreateRectRgnIndirect(&rcToolbar);
CombineRgn(hRgn1, hRgn1, hRgn2, RGN_DIFF);
FillRgn(hdc, hRgn1, brush);
DeleteObject(hRgn1);
DeleteObject(hRgn2);
}
EndPaint(m_hwnd, &ps);
}
//-----------------------------------------------------------------------------
// MainWindow::OnSize
// Description: Called when the window is resized.
//-----------------------------------------------------------------------------
void MainWindow::OnSize()
{
// resize the toolbar
SendMessage(toolbar.Window(), WM_SIZE, 0, 0);
// resize the rebar
SendMessage(rebar.Window(), WM_SIZE, 0, 0);
RECT rcWindow;
RECT rcControl;
// Find the client area of the application.
GetClientRect(m_hwnd, &rcWindow);
// Subtract the area of the rebar control.
GetClientRect(rebar.Window(), &rcControl);
SubtractRect(&rcWindow, &rcWindow, &rcControl);
// What's left is the area for the video. Notify the player.
m_pPlayer->UpdateVideoWindow(&rcWindow);
}
//-----------------------------------------------------------------------------
// MainWindow::OnTimer
// Description: Called when the timer elapses.
//-----------------------------------------------------------------------------
void MainWindow::OnTimer()
{
// If the player can seek, update the seek bar with the current position.
if (m_pPlayer->CanSeek())
{
REFERENCE_TIME timeNow;
if (SUCCEEDED(m_pPlayer->GetCurrentPosition(&timeNow)))
{
seekbar.SetPosition((LONG)(timeNow / ONE_MSEC));
}
}
}
//-----------------------------------------------------------------------------
// MainWindow::OnWmNotify
// Description: Handle WM_NOTIFY messages.
//-----------------------------------------------------------------------------
void MainWindow::OnWmNotify(const NMHDR *pHdr)
{
switch (pHdr->code)
{
case TTN_GETDISPINFO:
// Display tool tips
toolbar.ShowToolTip((NMTTDISPINFO*)pHdr);
break;
default:
switch (pHdr->idFrom)
{
case IDC_SEEKBAR:
OnSeekbarNotify((NMSLIDER_INFO*)pHdr);
break;
case IDC_VOLUME:
OnVolumeSliderNotify((NMSLIDER_INFO*)pHdr);
break;
}
break;
}
}
//-----------------------------------------------------------------------------
// MainWindow::OnSeekbarNotify
// Description: Handle WM_NOTIFY messages from the seekbar.
//-----------------------------------------------------------------------------
void MainWindow::OnSeekbarNotify(const NMSLIDER_INFO *pInfo)
{
static PlaybackState state = STATE_CLOSED;
// Pause when the scroll action begins.
if (pInfo->hdr.code == SLIDER_NOTIFY_SELECT)
{
state = m_pPlayer->State();
m_pPlayer->Pause();
}
// Update the position continuously.
m_pPlayer->SetPosition(ONE_MSEC * pInfo->position);
// Restore the state at the end.
if (pInfo->hdr.code == SLIDER_NOTIFY_RELEASE)
{
if (state == STATE_STOPPED)
{
m_pPlayer->Stop();
}
else if (state == STATE_RUNNING)
{
m_pPlayer->Play();
}
}
}
//-----------------------------------------------------------------------------
// MainWindow::OnVolumeSliderNotify
// Description: Handle WM_NOTIFY messages from the volume slider.
//-----------------------------------------------------------------------------
void MainWindow::OnVolumeSliderNotify(const NMSLIDER_INFO *pInfo)
{
m_pPlayer->SetVolume(pInfo->position);
}
//-----------------------------------------------------------------------------
// MainWindow::OnFileOpen
// Description: Open a new file for playback.
//-----------------------------------------------------------------------------
void MainWindow::OnFileOpen()
{
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
WCHAR szFileName[MAX_PATH];
szFileName[0] = L'\0';
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hwnd;
ofn.hInstance = m_hInstance;
ofn.lpstrFilter = L"All (*.*)/0*.*/0";
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_FILEMUSTEXIST;
HRESULT hr;
if (GetOpenFileName(&ofn))
{
hr = m_pPlayer->OpenFile(szFileName);
// Update the state of the UI.
UpdateUI();
// Invalidate the appliction window, in case there is an old video
// frame from the previous file and there is no video now. (eg, the
// new file is audio only, or we failed to open this file.)
InvalidateRect(m_hwnd, NULL, FALSE);
// Update the seek bar to match the current state.
UpdateSeekBar();
if (SUCCEEDED(hr))
{
// If this file has a video stream, we need to notify
// the VMR about the size of the destination rectangle.
// Invoking our OnSize() handler does this.
OnSize();
}
else
{
NotifyError(m_hwnd, TEXT("Cannot open this file."), hr);
}
}
}
//-----------------------------------------------------------------------------
// MainWindow::OnPlay
// Description: Start playback.
//-----------------------------------------------------------------------------
void MainWindow::OnPlay()
{
m_pPlayer->Play();
UpdateUI();
}
//-----------------------------------------------------------------------------
// MainWindow::OnStop
// Description: Stop playback.
//-----------------------------------------------------------------------------
void MainWindow::OnStop()
{
HRESULT hr = m_pPlayer->Stop();
// Seek back to the start.
if (SUCCEEDED(hr))
{
if (m_pPlayer->CanSeek())
{
hr = m_pPlayer->SetPosition(0);
}
}
UpdateUI();
}
//-----------------------------------------------------------------------------
// MainWindow::OnPause
// Description: Pause playback.
//-----------------------------------------------------------------------------
void MainWindow::OnPause()
{
m_pPlayer->Pause();
UpdateUI();
}
//-----------------------------------------------------------------------------
// MainWindow::OnMute
// Description: Toggle the muted / unmuted state.
//-----------------------------------------------------------------------------
void MainWindow::OnMute()
{
if (m_pPlayer->IsMuted())
{
m_pPlayer->Mute(FALSE);
toolbar.SetButtonImage(IDC_BUTTON_MUTE, ID_IMAGE_MUTE_OFF);
}
else
{
m_pPlayer->Mute(TRUE);
toolbar.SetButtonImage(IDC_BUTTON_MUTE, ID_IMAGE_MUTE_ON);
}
}
//-----------------------------------------------------------------------------
// MainWindow::OnGraphEvent
// Description: Callback to handle events from the filter graph.
//-----------------------------------------------------------------------------
// ! It is very important that the application does not tear down the graph inside this
// callback.
void MainWindow::OnGraphEvent(long eventCode, LONG_PTR param1, LONG_PTR param2)
{
switch (eventCode)
{
case EC_COMPLETE:
OnStop();
break;
}
}
//-----------------------------------------------------------------------------
// MainWindow::UpdateUI
// Description: Update the UI based on the current playback state.
//-----------------------------------------------------------------------------
void MainWindow::UpdateUI()
{
BOOL bPlay = FALSE;
BOOL bPause = FALSE;
BOOL bStop = FALSE;
switch (m_pPlayer->State())
{
case STATE_RUNNING:
bPause = TRUE;
bStop = TRUE;
break;
case STATE_PAUSED:
bPlay = TRUE;
bStop = TRUE;
break;
case STATE_STOPPED:
bPlay = TRUE;
break;
}
toolbar.Enable(IDC_BUTTON_PLAY, bPlay);
toolbar.Enable(IDC_BUTTON_PAUSE, bPause);
toolbar.Enable(IDC_BUTTON_STOP, bStop);
}
//-----------------------------------------------------------------------------
// MainWindow::UpdateSeekBar
// Description: Update the seekbar based on the current playback state.
//-----------------------------------------------------------------------------
void MainWindow::UpdateSeekBar()
{
// If the player can seek, set the seekbar range and start the time.
// Otherwise, disable the seekbar.
if (m_pPlayer->CanSeek())
{
seekbar.Enable(TRUE);
LONGLONG rtDuration = 0;
m_pPlayer->GetDuration(&rtDuration);
seekbar.SetRange(0, (LONG)(rtDuration / ONE_MSEC));
// Start the timer
m_timerID = SetTimer(m_hwnd, IDT_TIMER1, TICK_FREQ, NULL);
}
else
{
seekbar.Enable(TRUE);// FALSE);
// Stop the old timer, if any.
StopTimer();
}
}
//-----------------------------------------------------------------------------
// MainWindow::StopTimer
// Description: Stops the timer.
//-----------------------------------------------------------------------------
void MainWindow::StopTimer()
{
if (m_timerID != 0)
{
KillTimer(m_hwnd, m_timerID);
m_timerID = 0;
}
}
void NotifyError(HWND hwnd, TCHAR* sMessage, HRESULT hrStatus)
{
TCHAR sTmp[512];
HRESULT hr = StringCchPrintf(sTmp, 512, TEXT("%s hr = 0x%X"), sMessage, hrStatus);
if (SUCCEEDED(hr))
{
MessageBox(hwnd, sTmp, TEXT("Error"), MB_OK | MB_ICONERROR);
}
}