// JavuPlayerCtrl.cpp : Implementation of CJavuPlayerCtrl // // Written by Will Braynen (in 2001) #include "stdafx.h" #include "JavuPlayerX.h" #include "JavuPlayerCtrl.h" #include DWORD WINAPI PlayFastThreadStub (LPVOID lpParameter); void CALLBACK SynchMoveSliderStub (UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2); //////////////////////////////// SIMPLIFIED FLOW-CHART ///////////////////////////////////// // // fastReverse -> OnFastReverse -> Play( current rate * 2 ) -> PlayReverse -> PlayFastThread // fastForward -> OnFastForward -> Play( current rate * 2 ) -> PlayForward -> if rate <= AUDIO_PLAYBACK_RATE_MAX then calls IMediaSeeking::SetRate, else uses PlayFastThread. // play -> Play( 1.0 ) -> PlayForward ( 1.0 ) // put_Rate (Rate property) -> Play -> if rate is 0 then calls OnPause, if rate < 0 then calls PlayReverse, else if rate > 0 then calls PlayForward (see above) // put_FileName (FileName property) -> LoadFile -> PrepareMedia, GetBitRate, GetTotalDurationTime, GetTotalDurationFrame, Play or OnStop, EnableGUI // pause -> OnPause -> PauseMedia -> IMediaControl::Pause // stop -> OnStop -> StopMedia -> IMediaControl::Stop // // OnClickedButton_jump -> OnJump -> JumpTo -> SynchMoveSlider -> DisplayCurrentPosition (gets current position from...) -> GetCurrentPosition -> MediaSeeking::GetCurrentPosition // put_CurrentPosition... -> JumpTo -> etc. // OnHScroll -> JumpTo -> etc. // HRESULT CJavuPlayerCtrl::OnFastForward() { double rate = m_dPlaybackRate; if (PlayerState_PlayingForward != m_playerState) { // Set initial fast foward speed rate = 2.0; } else { // If this button was pressed just before, double the speed rate *= 2; } return Play (rate); } HRESULT CJavuPlayerCtrl::OnFastReverse() { double rate = m_dPlaybackRate; if (PlayerState_PlayingReverse != m_playerState) { // Set initial fast reverse speed rate = -2.0; } else { // If this button was pressed just before, double the speed rate *= 2; } return Play (rate); } HRESULT CJavuPlayerCtrl::OnPause() { player_state_t oldPlayerState = m_playerState; // to fire StateChange if appropriate m_playerState = PlayerState_Paused; m_dPlaybackRate = 0; StopPlayFastThread(); StopTimer (m_wSeekSliderTimerID); HRESULT hr = PauseMedia(); if (FAILED(hr)) { DisplayStatus(TEXT("Pause failed")); return hr; } // Wait for the pause to propagate to all filters OAFilterState fs; hr = pMC->GetState(500, &fs); // Make sure we're in the beginning or middle of the current frame (depending on // whether the SEEK_INTO_MIDDLE_OF_FRAME preprocessor variable is defined) //NormalizeCurrentPosition(); m_dPlaybackRate = 0; SynchMoveSlider(); DisplayStatus (TEXT("Paused")); if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return hr; } HRESULT CJavuPlayerCtrl::OnStop(bool bPositionToBeginning) { LONGLONG llOldPosition = m_rtCurrentPositionTime; // to fire PositionChange if appropriate player_state_t oldPlayerState = m_playerState; // to fire StateChange if appropriate m_playerState = PlayerState_Stopped; m_dPlaybackRate = 0; StopPlayFastThread(); StopTimer (m_wSeekSliderTimerID); // Stop playback immediately with IMediaControl::Stop(). HRESULT hr = StopMedia(); if (FAILED(hr)) { DisplayStatus(TEXT("Stop failed")); return hr; } // Wait for the stop to propagate to all filters OAFilterState fs; hr = pMC->GetState(500, &fs); // Reset to beginning of media clip if (bPositionToBeginning) { #ifdef SEEK_INTO_MIDDLE_OF_FRAME // // Position to the middle of the first frame // to avoid rounding error during positioning in Jump() // LONGLONG llPosition = (LONGLONG)(FRAME_DURATION / 2); hr = pMS->SetPositions ( &llPosition, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); #else LONGLONG llPosition = 0; hr = pMS->SetPositions(&llPosition, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); #endif } // Display the first frame of the media clip, if it contains video. // StopWhenReady() pauses all filters internally (which allows the video // renderer to queue and display the first video frame), after which // it sets the filters to the stopped state. This enables easy preview // of the video's poster frame. hr = pMC->StopWhenReady(); SynchMoveSlider(); DisplayStatus (TEXT("Stopped")); if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return hr; } HRESULT CJavuPlayerCtrl::PauseMedia() { if ( ! pMC) return E_POINTER; // If we're already paused, don't bother if(State_Paused == m_psCurrent) return S_OK; HRESULT hr = pMC->Pause(); if (FAILED(hr)) return hr; // Remember play state m_psCurrent = State_Paused; return hr; } HRESULT CJavuPlayerCtrl::StopMedia() { if ( ! pMC) return E_POINTER; // If we're already stopped, don't bother if (State_Stopped == m_psCurrent) return S_OK; // Stop playback HRESULT hr = pMC->Stop(); if (FAILED(hr)) return hr; // Remember play state m_psCurrent = State_Stopped; return hr; } HRESULT CJavuPlayerCtrl::ResumeAudio() { HRESULT hr=S_OK; IBasicAudio *pBA=NULL; long lVolume; if (!pGB) return S_OK; hr = pGB->QueryInterface(IID_IBasicAudio, (void **)&pBA); if (FAILED(hr)) return S_OK; // Read current volume hr = pBA->get_Volume(&lVolume); if (hr == E_NOTIMPL) { // Fail quietly if this is a video-only media file pBA->Release(); return hr; } else if (FAILED(hr)) { MessageBox (_T("pBA->get_Volume failed!")); //deBug pBA->Release(); return hr; } lVolume = VOLUME_FULL; #ifdef DEBUG_OUTPUT MessageBox (_T("Media is resuming normal audio"), _T("ResumeAudio()")); #endif // Set new volume hr = pBA->put_Volume(lVolume); if (FAILED(hr)) { MessageBox (_T("pBA->put_Volume failed!")); //deBug } pBA->Release(); return hr; } HRESULT CJavuPlayerCtrl::MuteAudio() { assert (false); // not yet used HRESULT hr=S_OK; IBasicAudio *pBA=NULL; long lVolume; if (!pGB) return S_OK; hr = pGB->QueryInterface(IID_IBasicAudio, (void **)&pBA); if (FAILED(hr)) return S_OK; // Read current volume hr = pBA->get_Volume(&lVolume); if (hr == E_NOTIMPL) { // Fail quietly if this is a video-only media file pBA->Release(); return hr; } else if (FAILED(hr)) { ///-INSERT CODE HERE -- Display error message pBA->Release(); return hr; } lVolume = VOLUME_SILENCE; // Set new volume hr = pBA->put_Volume(lVolume); pBA->Release(); return hr; } ///////////////////////////////////////////////////////// // // If PrepareMedia() returns successfully, // copies lpszFilename into m_strFilename. // // Returns E_INVALIDARG // if the file doesn't exist or is a directory. // HRESULT CJavuPlayerCtrl::LoadFile(LPCWSTR lpszFilename) { // check arguments if ( ! lpszFilename) return E_INVALIDARG; // stop playback (if any) and remember the playback rate in case you want to start playing the new file at the same rate (forward only) double dOldPlaybackRate = m_dPlaybackRate; OnStop(); HRESULT hr; player_state_t oldPlayerState = m_playerState; EnableGUI(false); m_playerState = PlayerState_Loading; DisplayStatus (TEXT("Loading...")); if (m_bStateChangeNotification) Fire_StateChange(oldPlayerState, PlayerState_Loading); // Is this a valid filename? if ( ! IsValidFile (lpszFilename)) { FreeDirectShow(); DisplayStatus(TEXT("Invalid filename!")); if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return E_INVALIDARG; } // Reset any file-specific information m_rtTotalDurationTime = 0; m_llTotalDurationFrame = 0; m_rtAveTimePerFrame = 0; m_dFrameRate = 0.0; m_lBitRate = 0; m_bAudioOnly = false; // this is settled in PrepareMedia() ::SetWindowText (::GetDlgItem (m_hWnd, IDC_CURRENTPOSITION), _T("hh:mm:ss:ff")); // erase "hh:mm:ss:ff" since it might be left over from the previous video file // Remember current play state to restart playback int nCurrentState = m_psCurrent; // Load the selected media file hr = PrepareMedia (lpszFilename); if (FAILED(hr)) { FreeDirectShow(); DisplayStatus(TEXT("File failed to render!")); m_playerState = PlayerState_LoadFailed; if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return hr; } GetBitRate (&m_lBitRate); // GetBitRate -> EnumPins... gets m_rtAveTimePerFrame from the video header // How long is the file (100ns)? hr = GetTotalDurationTime (&m_rtTotalDurationTime); if (FAILED(hr)) { FreeDirectShow(); DisplayStatus(TEXT("Load failed! (unknown duration)")); m_playerState = PlayerState_LoadFailed; if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return hr; } if (pTimeline) { // // Using DES // if (wcsstr (lpszFilename, _T(".xtl"))) { m_dFrameRate = GetXMLFrameRate (lpszFilename); } m_rtAveTimePerFrame = (LONGLONG)(10000000 / m_dFrameRate); m_llTotalDurationFrame = (LONGLONG)(m_rtTotalDurationTime / m_rtAveTimePerFrame + (m_rtTotalDurationTime % m_rtAveTimePerFrame ? 1 : 0)); // m_llTotalDurationFrame = rounded up (total time duration / average time per frame) } else { // // Not using DES // // How long is the file (total number of frames)? if (!m_bAudioOnly) { hr = GetTotalDurationFrame (&m_llTotalDurationFrame); if (FAILED(hr)) { //MessageBox (_T("Frame information might not be available.")); } // Even if GetTotalDurationFrame fails and we don't know how many frames there are, go on anyway since there is still a lot we can do (e.g. playback, seek) } // Display total duration - right now IDC_TOTALDURATION is hidden, so you won't see anything CString strTotalDuration; Time2Timecode (m_rtTotalDurationTime, &strTotalDuration, m_dFrameRate); ::SetWindowText (::GetDlgItem (m_hWnd, IDC_TOTALDURATION), strTotalDuration); // If we didn't find m_rtAveTimePerFrame in the file header, then we'll try to figure it out ourselves if (!m_bAudioOnly && m_rtAveTimePerFrame <= 0) { // If the file is an MPEG, this variable was set in EnumPins() to the info found in the video header; // however, if this isn't an MPEG, then we need to set it here, before we use it. if (m_llTotalDurationFrame > 0) { m_rtAveTimePerFrame = m_rtTotalDurationTime / m_llTotalDurationFrame; } else { // see EnableGUI() - certain GUI functionality might be disabled //MessageBox (_T("Unknown number of frames (and can't find m_rtAveTimePerFrame in the file header)"), _T("Javu Player")); } } // // What's the frame rate? // m_dFrameRate = m_rtAveTimePerFrame > 0 ? SET_PRECISION (10000000.0 / m_rtAveTimePerFrame, 2) : 0; //(double)((m_llTotalDurationFrame * 10000000) / m_rtTotalDurationTime); } DisplayFrameRate (m_dFrameRate); // If the user has asked to mute audio then we // need to mute this new clip before continuing. if (m_bMute) MuteAudio(); if (!m_bAudioOnly) { DisplayStatus(TEXT("Ready")); } else { DisplayStatus(TEXT("Ready (audio only file)")); } // If we were running when the user changed selection, // start running the newly selected clip if (dOldPlaybackRate > 0) { Play (m_dPlaybackRate = dOldPlaybackRate); } else { OnStop(); } m_strFilename.Format (_T("%s"), lpszFilename); // set filename EnableGUI(); m_playerState = PlayerState_Ready; if (m_bNewStreamNotification) Fire_NewStream(); if (m_bStateChangeNotification) Fire_StateChange(PlayerState_Loading, PlayerState_Ready); // Make it so that the graph can be loaded from an external process (e.g. GraphEdit: run the player and then run GraphEdit; in GraphEdit, do File->Connect) hr = AddToRot(pGB, &m_dwRegister); return hr; } HRESULT CJavuPlayerCtrl::EnumPins(IBaseFilter *pFilter, PIN_DIRECTION PinDir, long *plBitRate=NULL) { // enforce the pre-condition if ( ! pGB) return E_POINTER; if ( ! pFilter) return E_INVALIDARG; HRESULT hr; IEnumPins *pEnum = NULL; IPin *pPin = NULL; AM_MEDIA_TYPE mediaType; // for getting the bit rate // Get pin enumerator hr = pFilter->EnumPins(&pEnum); if (FAILED(hr)) { return hr; } // Enumerate all pins on this filter while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; hr = pPin->QueryDirection(&PinDirThis); if (FAILED(hr)) { pPin->Release(); continue; } // Does the pin's direction match the requested direction? if (PinDir == PinDirThis) { PIN_INFO pininfo={0}; // Direction matches, so add pin name to listbox hr = pPin->QueryPinInfo(&pininfo); if (SUCCEEDED(hr)) { hr = pPin->ConnectionMediaType (&mediaType); // ??? should FreeMediaType(mediaType) be used at the end of the loop? // // Get the bit rate // if (plBitRate) { if (SUCCEEDED(hr) && (mediaType.pbFormat != NULL) && (MEDIATYPE_Video == mediaType.majortype)) // it's a video filter { *plBitRate = ((VIDEOINFOHEADER *)(mediaType.pbFormat))->dwBitRate; REFERENCE_TIME rtAveTimePerFrame = ((VIDEOINFOHEADER *)(mediaType.pbFormat))->AvgTimePerFrame; if (rtAveTimePerFrame > 0) { m_rtAveTimePerFrame = rtAveTimePerFrame; } } // end if (it's video) } // end if (get bit rate) ///-FreeMediaType (mediaType); } // end if (IPin::QueryPinInfo SUCCEEDED) // The pininfo structure contains a reference to an IBaseFilter, // so you must release its reference to prevent resource a leak. pininfo.pFilter->Release(); } pPin->Release(); } pEnum->Release(); return hr; } HRESULT CJavuPlayerCtrl::GetBitRate(long *plBitRate) { // Enforce the pre-condition if ( ! pGB) return E_POINTER; if ( ! plBitRate) return E_INVALIDARG; HRESULT hr; IEnumFilters *pEnum = NULL; IBaseFilter *pFilter = NULL; ULONG cFetched; *plBitRate = 0; // Get filter enumerator hr = pGB->EnumFilters(&pEnum); if (FAILED(hr)) { return hr; } // Enumerate all filters in the graph while(pEnum->Next(1, &pFilter, &cFetched) == S_OK) { // Added by Alexz FILTER_INFO FilterInfo; hr = pFilter->QueryFilterInfo(&FilterInfo); hr = FilterInfo.pGraph->Release(); // Added by Alexz // Enumerate all INPUT pins on the filter and try to get the bit rate hr = EnumPins (pFilter, PINDIR_INPUT, plBitRate); if (*plBitRate > 0) { pFilter->Release(); pEnum->Release(); return hr; // EnumPins returned a bit rate, which is now in plBitRate. // This means we found the MPEG decoder filter, so no need to look at other filters and pins. } pFilter->Release(); } pEnum->Release(); return hr; } void CJavuPlayerCtrl::StopPlayFastThread() { if (m_hPlayFastThread) { m_playerState = PlayerState_StoppingPlayFastThread; Sleep (10); // hopefully, by the time we wake up, FastPlayThread will have exited. } } ///////////////////////////////////////////////////////////////////////// // // Pre-condition: { lpszFilename must be a valid file (and not a directory). } // // lpszFilename must be the entire path, including the directory, as well as // the machine name (unless the file resides locally). // REMINDER: Don't forget to escape the slashes. // SAMPLE USAGE: PrepareMedia(L"\\\\Intranet\\downloads\\will's test\\eli_test_5000.MPG") // -- in the above, the machine name is "Intranet", // the share on the machine is "downloads", // the directory is "will's test" // and the filename is "eli_test_5000.MPG" // HRESULT CJavuPlayerCtrl::PrepareMedia(LPCWSTR lpszFilename) { // check arguments if ( ! lpszFilename) return E_INVALIDARG; HRESULT hr; // First release any existing interfaces ResetDirectShow(); if ( ! wcsstr (lpszFilename, _T(".xtl"))) { // // This is a regular media file - do NOT use DES // // // Get the interfaces // JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGB)); JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC)); JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS)); JIF(pGB->QueryInterface(IID_IMediaPosition, (void **)&pMP)); JIF(pGB->QueryInterface(IID_IBasicVideo, (void **)&pBV)); JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW)); JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME)); // Allow DirectShow to create the FilterGraph for this media file hr = pGB->RenderFile(lpszFilename, NULL); } else { // // it's an .xtl file - use DES // // create a timeline hr = CoCreateInstance(__uuidof(AMTimeline), NULL, CLSCTX_INPROC_SERVER, __uuidof(IAMTimeline), (void**)&pTimeline); // create an XML parser and read the XML file if (SUCCEEDED(hr)) hr = CoCreateInstance(__uuidof(Xml2Dex), NULL, CLSCTX_INPROC_SERVER, __uuidof(IXml2Dex), (void**)&pXml); if (SUCCEEDED(hr)) hr = pXml->ReadXMLFile(pTimeline, (LPTSTR)lpszFilename); // create a rendering engine if (SUCCEEDED(hr)) hr = CoCreateInstance(__uuidof(RenderEngine), NULL, CLSCTX_INPROC_SERVER, __uuidof(IRenderEngine), (void**) &pRender); // build the graph if (SUCCEEDED(hr)) hr = pRender->SetTimelineObject(pTimeline); if (SUCCEEDED(hr)) hr = pRender->ConnectFrontEnd(); if (SUCCEEDED(hr)) hr = pRender->RenderOutputPins(); // get the filter graph DES built if (SUCCEEDED(hr)) hr = pRender->GetFilterGraph(&pGB); else return hr; // // Get the interfaces // JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC)); // You should first run Filtergraph for a while, for the engine to load all source and decode filters; Alexz hr = pMC->Run(); hr = pMC->StopWhenReady(); JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS)); JIF(pGB->QueryInterface(IID_IMediaPosition, (void **)&pMP)); JIF(pGB->QueryInterface(IID_IBasicVideo, (void **)&pBV)); JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW)); JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME)); } InitVideoWindow (pVW); hr = pMS->SetTimeFormat (&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) return hr; // This hasn't happened yet, but it's probably not impossible. return hr; CLEANUP: FreeDirectShow(); return(hr); } void CJavuPlayerCtrl::CenterVideo() { HRESULT hr; RECT rc; if ((m_bAudioOnly) || (!pVW)) return; // Read coordinates of video container window ::GetClientRect (m_Screen, &rc); long video_width, video_height; if (m_bPreserveAspectRatio) { GetPreservedAspectRatio (video_width, video_height); } else { video_width = m_lScreenWidth; video_height = m_lScreenHeight; } // Find the center point of the screen long cx = m_lScreenWidth / 2, cy = m_lScreenHeight / 2; // Where should the video be displayed long x = cx - (video_width / 2), y = cy - (video_height / 2); hr = pVW->SetWindowPosition (x, y, video_width, video_height); if (FAILED(hr)) { //RetailOutput(TEXT("Failed to set window position! hr=0x%x\r\n"), hr); return; } repaint(); } /////////////////////////////////////////////////////////////////// // // Returns the width and height of the video so that it would fit // into a window of m_lScreenWidth by m_lScreenHeight dimensions // while preserving the aspect ratio. // HRESULT CJavuPlayerCtrl::GetPreservedAspectRatio(long& lVideoWidth, long& lVideoHeight) { // What's the screen size? RECT rect; ::GetWindowRect (m_Screen, &rect); m_lScreenWidth = rect.right - rect.left; m_lScreenHeight = rect.bottom - rect.top; // Read the default video size lVideoWidth = lVideoHeight = 0; pBV->GetVideoSize(&lVideoWidth, &lVideoHeight); int smaller = m_lScreenWidth < m_lScreenHeight ? m_lScreenWidth : m_lScreenHeight; int bigger = m_lScreenWidth > m_lScreenHeight ? m_lScreenWidth : m_lScreenHeight; if (lVideoHeight == lVideoWidth) { lVideoHeight = lVideoWidth = smaller; } else if (lVideoHeight < lVideoWidth) { if (((double)lVideoWidth / lVideoHeight) < ((double)m_lScreenWidth / m_lScreenHeight)) { lVideoWidth = m_lScreenHeight * lVideoWidth / lVideoHeight; lVideoHeight = m_lScreenHeight; } else if (((double)lVideoWidth / lVideoHeight) > ((double)m_lScreenWidth / m_lScreenHeight)) { lVideoHeight = m_lScreenWidth * lVideoHeight / lVideoWidth; lVideoWidth = m_lScreenWidth; } else // (((double)lVideoWidth / lVideoHeight) == ((double)m_lScreenWidth / m_lScreenHeight)) { lVideoHeight = m_lScreenHeight; lVideoWidth = m_lScreenWidth; } } else { #ifdef DEBUG_OUTPUT MessageBox (_T("width < height\nThis case hasn't been tested!"), _T("GetPreservedAspectRatio()")); #endif lVideoWidth = lVideoWidth * m_lScreenHeight / lVideoHeight; lVideoHeight = m_lScreenHeight; } return S_OK; } HRESULT CJavuPlayerCtrl::HandleGraphEvent() { LONG evCode, evParam1, evParam2; HRESULT hr=S_OK; if (NULL == pME) return E_POINTER; // bug fix: FastFoward --> Quit (without pressing Stop), results in "Unhandled Exception" or "Access Violation" while(SUCCEEDED(pME->GetEvent(&evCode, &evParam1, &evParam2, 0))) { // Spin through the events hr = pME->FreeEventParams(evCode, evParam1, evParam2); if(EC_COMPLETE == evCode) { HandleECComplete(); } } return hr; } ////////////////////////////////////////////////////////////////////// // // Pre-condition: { current time format is TIME_FORMAT_MEDIA_TIME } // // Blocks on a call to RenderCurrentFrame(), so when Jump() returns, // either RenderCurrentFrame() timed out or, hopefully, the frame was already rendered. // // Returns S_FALSE // if you're doing a relative jump (bAbsoluteJump = false) // and you're already on the first frame // // Sample usage: // Jump( 50, &TIME_FORMAT_FRAME, true ) -- jump to the 50th frame in the file (counting from the beginning) // Jump( -1, &TIME_FORMAT_FRAME, false ) -- jump one frame back // Jump( 1, &TIME_FORMAT_FRAME, false ) -- jump one frame forward // HRESULT CJavuPlayerCtrl::Jump(REFERENCE_TIME rtPosition, const GUID *pFormat, bool bAbsoluteJump) { // make sure DirectShow is initialized if ( ! pMS) return E_POINTER; // check arguments if ( ! pFormat) return E_INVALIDARG; HRESULT hr; REFERENCE_TIME rtCurrentPosition; bool bBeginningOfFile = false, bEndOfFile = false; // // Assertion: { current time format is TIME_FORMAT_MEDIA_TIME } // if (m_bAudioOnly) return E_FAIL; // no jumps in audio-only files for now if ((TIME_FORMAT_FRAME == *pFormat) && (FRAME_DURATION <= 0)) return E_FAIL; // wouldn't know how to convert the frame # to 100ns // This function doesn't support TIME_FORMAT_BYTE and of course TIME_FORMAT_NONE if ((TIME_FORMAT_FRAME != *pFormat) && (TIME_FORMAT_MEDIA_TIME != *pFormat)) return E_INVALIDARG; if ((0 == rtPosition) && ( ! bAbsoluteJump)) return E_INVALIDARG; // // Pause the graph // PauseMedia(); // // Absolute jump // if (bAbsoluteJump) { // negative frame arguments are meaningless for an absolute jump if (rtPosition < 0) return E_INVALIDARG; // count from the beginning rtCurrentPosition = (LONGLONG)(((TIME_FORMAT_FRAME == *pFormat) && (pTimeline)) ? FRAME_DURATION / 4 : 0); } // // Relative jump // else { // what's our current position hr = GetCurrentPosition (&rtCurrentPosition, NULL); if (FAILED(hr)) return hr; } // // Calculate the new position // rtCurrentPosition += (TIME_FORMAT_MEDIA_TIME == *pFormat ? rtPosition : (LONGLONG)(FRAME_DURATION * rtPosition)); // // Make sure you don't seek before the beggining or past the end of file! // if (rtCurrentPosition > (m_rtTotalDurationTime - FRAME_DURATION / 2)) { // Seeking to or past the end bEndOfFile = true; rtCurrentPosition = (LONGLONG)(m_rtTotalDurationTime - FRAME_DURATION / 2); } if (rtCurrentPosition < 0) { // Seeking to or before the beginning bBeginningOfFile = true; rtCurrentPosition = 0; } // // Seek to the new position // if (rtCurrentPosition >= 0) { hr = pMS->SetPositions ( &rtCurrentPosition, AM_SEEKING_AbsolutePositioning, // | AM_SEEKING_Segment - WHAT IS IT? NULL, AM_SEEKING_NoPositioning); if (FAILED(hr)) return hr; } if (FAILED(hr)) return hr; // hr might be VFW_S_STATE_INTERMEDIATE, which is > 0 // Wait for the frame to render hr = RenderCurrentFrame(); // Move the slider SynchMoveSlider(); if (bBeginningOfFile || bEndOfFile) { return S_FALSE; // We're at the beginning/end of file already (used by PlayFastThread to determine when fast playback is finished) } return hr; } HRESULT CJavuPlayerCtrl::Play(double dPlaybackRate) { player_state_t oldPlayerState = m_playerState; HRESULT hr; if ( ! pMC) return E_POINTER; // If there is no video, do NOT exceed AUDIO_PLAYBACK_RATE_MAX and don't try to play backwards if ((m_bAudioOnly) && ((dPlaybackRate > AUDIO_PLAYBACK_RATE_MAX) || (dPlaybackRate < 0))) { return E_INVALIDARG; } else if (ABS(dPlaybackRate) > VIDEO_PLAYBACK_RATE_MAX) { return E_INVALIDARG; } double dOldPlaybackRate = m_dPlaybackRate; // to fire PlaybackRateChange if appropriate // // Pause or play (backward/forward) // if (0 == dPlaybackRate) { hr = OnPause(); } else if (dPlaybackRate > 0) { hr = PlayForward(dPlaybackRate); } else if (dPlaybackRate < 0) { hr = PlayReverse(-dPlaybackRate); } // // Display a status message // if (SUCCEEDED(hr)) { m_dPlaybackRate = dPlaybackRate; CString msg; if (dPlaybackRate != 0) { msg.Format (_T("Playing at %.1fx"), dPlaybackRate); DisplayStatus (msg); } // else if dPlaybackRate == 0, then Pause() displays "Paused" on status bar // if the playback rate changed, fire a PlaybackRateChange event if (m_bRateChangeNotification && (dOldPlaybackRate != m_dPlaybackRate)) Fire_RateChange (dOldPlaybackRate, m_dPlaybackRate); // fire a StateChange event if we started or stopped playing or changed the direction of playback (forward to reverse or vice versa) if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); } return hr; } ///////////////////////////////////////////////////////////////////////// // // if dRate <= AUDIO_PLAYBACK_RATE_MAX, calls IMediaSeeking::SetRate() // else uses CJavuPlayerCtrl::PlayFastThread() routine to fast-forward. // HRESULT CJavuPlayerCtrl::PlayForward(double dRate) { HRESULT hr = S_OK; // Enforce the pre-condition if (dRate <= 0) return E_INVALIDARG; // If there is no video, do NOT exceed AUDIO_PLAYBACK_RATE_MAX if (m_bAudioOnly && (dRate > AUDIO_PLAYBACK_RATE_MAX)) return E_INVALIDARG; // If there is video, do NOT exceed VIDEO_PLAYBACK_RATE_MAX if (dRate > VIDEO_PLAYBACK_RATE_MAX) return E_INVALIDARG; // // For some reason, if we don't do this, then the following ALWAYS occurs: // if PlayFastThread is running (for example if the user presses FastReverse or FastForward >= 2x), // then pressing Play right after plays with sound for a split second and then the sound disappears (?!) // if (m_hPlayFastThread && (dRate > 0) && (dRate <= AUDIO_PLAYBACK_RATE_MAX)) { StopPlayFastThread(); pMC->Stop(); // Wait for the stop to propagate to all filters OAFilterState fs; pMC->GetState(500, &fs); pMC->StopWhenReady(); } // // if we're at the end, rewind to the beginning // if (m_rtCurrentPositionTime > m_rtTotalDurationTime - 333000) { OnStop(); } if (dRate <= AUDIO_PLAYBACK_RATE_MAX) { hr = pMS->SetRate(dRate); if (FAILED(hr)) // Use PlayFastThread to simulate fast play { m_dPlaybackRate = dRate; // If we're playing in reverse already, modifying m_dPlaybackRate above was sufficient if (NULL == m_hPlayFastThread) { m_hPlayFastThread = CreateThread (NULL, 0, PlayFastThreadStub, this, 0, NULL); } return S_OK; } // Play hr = RunMedia(); if (FAILED(hr)) return hr; } else // Use PlayFastThread to simulate fast play { m_dPlaybackRate = dRate; // If we're playing in reverse already, modifying m_dPlaybackRate above was sufficient if (NULL == m_hPlayFastThread) m_hPlayFastThread = CreateThread (NULL, 0, PlayFastThreadStub, this, 0, NULL); } m_playerState = PlayerState_PlayingForward; // if something was wrong, should've returned already return S_OK; } ////////////////////////////////////////////////////////////////////// // // Pre-condition: { current time format is TIME_FORMAT_MEDIA_TIME } // // Use StopPlayFastThread() to stop; do NOT use TerminateThread(). // // Returns S_OK if everything is fine. // Returns E_INVALIDARG if dRate <= 0. // Returns E_FAIL if dRate exceeds FrameDurationMS. // HRESULT CJavuPlayerCtrl::PlayReverse(double dRate) { // Enforce the pre-condition if (dRate <= 0) return E_INVALIDARG; m_playerState = PlayerState_PlayingReverse; // // Too bad reverse playback is NOT supported by most filters. // Otherwise, we could just pass negative rates to IMediaSeeking::SetRate() // // Do not exceed VIDEO_PLAYBACK_RATE_MAX (talking in absolute values of course) if (dRate > VIDEO_PLAYBACK_RATE_MAX) return E_INVALIDARG; int tFrameDurationMS = (int)FRAME_DURATION / 10000; if (dRate > tFrameDurationMS) return E_FAIL; m_dPlaybackRate = -dRate; // If we're playing in reverse already, modifying m_dPlaybackRate above was sufficient if (NULL == m_hPlayFastThread) { m_hPlayFastThread = CreateThread (NULL, 0, PlayFastThreadStub, this, 0, NULL); } return S_OK; } /////////////////////////////////////////////////////////// // // Executed in a separate thread created in PlayReverse() // DWORD WINAPI PlayFastThreadStub (LPVOID lpParameter) { ((CJavuPlayerCtrl *)lpParameter)->PlayFastThread(); return 0; } void CJavuPlayerCtrl::PlayFastThread() { try { if ( ! pMS) return; HRESULT hr; int nSkipFrames = INITIAL_SKIP_FRAMES_GUESS; SYSTEMTIME st1, st2; int tDesiredFrameDurationMS, delayInterval; int tFrameDurationMS; double rate = 0; #ifdef DEBUG_OUTPUT // stats SYSTEMTIME total_playback_time1, total_playback_time2; bool bStopwatchAlreadyStarted = false; long lTotalFramesSkipped = 0; long lCount = 0; #endif // // Repeatedly back frame, dropping frames if necessary, until // (1) the beginning of the file is reached (Jump() returns S_FALSE), or // (2) some other button is pressed (g_hDlg->m_playerState != PlayerAction_PlayReverse), or // (3) this thread (m_hPlayFastThread) is killed with TerminateThread() // do { // // If someone changed the rate, reset and recalculate // if ((m_dPlaybackRate != rate) && (ABS(m_dPlaybackRate) <= VIDEO_PLAYBACK_RATE_MAX)) { if (m_dPlaybackRate > 0) { m_playerState = PlayerState_PlayingForward; } else if (m_dPlaybackRate < 0) { m_playerState = PlayerState_PlayingReverse; } else // if (0 == m_dPlaybackRate) { m_playerState = PlayerState_StoppingPlayFastThread; } // reset nSkipFrames only if (the absolute value of) the playback rate decreased if (ABS(m_dPlaybackRate) < ABS(rate)) { nSkipFrames = INITIAL_SKIP_FRAMES_GUESS; } rate = m_dPlaybackRate; // recalculate tFrameDurationMS = (int)FRAME_DURATION / 10000; // 100ns --> ms ( 1 ms = 1 000 000 ns = 10 000 * 100 ns ) // Just a precaution (probably matters when rate exceeds 30x) if (ABS(rate) > tFrameDurationMS) { assert (false); return; } tDesiredFrameDurationMS = (int)(tFrameDurationMS / ABS(rate)); // time interval between Jump calls delayInterval = tDesiredFrameDurationMS * nSkipFrames; } // // Jump to the next frame - Jump() renderes also; NextFrame() and PreviousFrame() are macros that call Jump() // GetLocalTime (&st1); // start stopwatch - time how long does it takes to seek and render one frame if (rate > 0) hr = NextFrame (nSkipFrames); else if (rate < 0) hr = PreviousFrame (nSkipFrames); else { m_playerState = PlayerState_StoppingPlayFastThread; break; } if ((S_FALSE == hr) || FAILED(hr)) { m_playerState = PlayerState_StoppingPlayFastThread; break; } GetLocalTime (&st2); // stop stopwatch // How long did it take to find and display the frame? long difftime = (st2.wMinute * 60 * 1000 + st2.wSecond * 1000 + st2.wMilliseconds) - (st1.wMinute * 60 * 1000 + st1.wSecond * 1000 + st1.wMilliseconds); if (difftime < 0) assert(false); //continue; // this can happen if the time unit one bigger than than biggest one used (e.g. minute/hour) rolls over if (delayInterval < difftime) { // if too long passed, start dropping / drop more frames; try again nSkipFrames++; // speeds up linearly delayInterval += tDesiredFrameDurationMS; } // don't sleep on (delayInterval == difftime) --> Sleep(0) because this might relinquish the remainder of the time slice (consult MSDN) and I'm not sure we want to do that (probably doesn't really matter though, because time slices are probably tiny) else if (delayInterval > difftime) { // if not enough time passed, wait #ifdef DEBUG_OUTPUT if (!bStopwatchAlreadyStarted) { // Start timing total playback once we're dropping enough frames (and not speeding up anymore) bStopwatchAlreadyStarted = true; GetLocalTime (&total_playback_time1); // start stopwatch - total playback } if (bStopwatchAlreadyStarted) { lTotalFramesSkipped += nSkipFrames; lCount++; } #endif // sleep, but no longer than one minute (just to be safe) if (delayInterval - difftime < 1000) Sleep (delayInterval - difftime); else Sleep (1000); } } while ((S_FALSE != hr) && (PlayerState_StoppingPlayFastThread != m_playerState)); m_hPlayFastThread = NULL; if (S_FALSE == hr) { // we stopped because we reached beginning or end of file OnStop(m_bPositionToBeginningWhenReachedEnd); SendMessage (WM_PLAYBACKENDED); } else if (PlayerState_StoppingPlayFastThread == m_playerState) { // we stopped because we were asked to SynchMoveSlider(); } else { // there is no other reason for us to stop, is there?! assert (false); } #ifdef DEBUG_OUTPUT // How long did the entire playback take? GetLocalTime (&total_playback_time2); // stop stopwatch long playbacktime = (total_playback_time2.wHour * 60 + total_playback_time2.wMinute * 60 + total_playback_time2.wSecond) - (total_playback_time1.wHour * 60 + total_playback_time1.wMinute * 60 + total_playback_time1.wSecond); CString msg; msg.Format (_T("Stats since level off:\nfast playback took %ld seconds\nshowing every %drd frame\n%ld total frames skipped\nlooped %ld times"), playbacktime, nSkipFrames, lTotalFramesSkipped, lCount); MessageBox (msg); #endif } catch (...) // trying to catch a dereference of a null pointer ("memory access violation") { StopTimer (m_wSeekSliderTimerID); #ifdef DEBUG_OUTPUT MessageBox (_T("caught an exception"), _T("PlayFastThread()")); #endif } } HRESULT CJavuPlayerCtrl::RunMedia() { if ( ! pMC) return E_POINTER; if (!m_wSeekSliderTimerID) { StartTimer(&m_wSeekSliderTimerID); } #ifdef DEBUG_OUTPUT GetLocalTime (&m_stPlaybackTimeStart); // start stopwatch #endif // Start playback HRESULT hr = pMC->Run(); if (FAILED(hr)) return hr; // Remember play state m_psCurrent = State_Running; return hr; } void CALLBACK SynchMoveSliderStub (UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1, DWORD dw2) { ((CJavuPlayerCtrl *)dwUser)->SynchMoveSlider(); } ///////////////////////////////////////////////////////// // // Positions the slider to reflect the current position // void CJavuPlayerCtrl::SynchMoveSlider() { REFERENCE_TIME rtNow; // IMediaSeeking *pMS could already be released, so be careful; try { // // It's probably best to have the current time format set to TIME_FORMAT_MEDIA_TIME (use pMS->SetTimeFormat); // searching in frames is too slow for large, compressed files and searching in bytes doesn't is a pain. // // hope pMS still hasn't gotten released, since the timer runs in a separate thread. pMS->GetCurrentPosition(&rtNow); long sliderTick = (long)((rtNow * TICKLEN) / (SAFE_DIVISOR(m_rtTotalDurationTime - FRAME_DURATION))); ::SendMessage (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), TBM_SETPOS, TRUE, sliderTick); DisplayCurrentPosition(); } catch (...) { StopTimer (m_wSeekSliderTimerID); #ifdef DEBUG_OUTPUT MessageBox (_T("caught an exception"), _T("SynchMoveSlider()")); #endif } } /////////////////////////////////////////////////////////////////////// // // Cancels the previous timer, if there was one and starts a new one. // void CJavuPlayerCtrl::StartTimer(MMRESULT *pwTimerID) { StopTimer (*pwTimerID); if (pwTimerID == &m_wSeekSliderTimerID) { *pwTimerID = timeSetEvent(SLIDER_TIMERDELAY, 50, SynchMoveSliderStub, ( unsigned long )this, TIME_PERIODIC); } } void CJavuPlayerCtrl::StopTimer(MMRESULT& wTimerID) { if(wTimerID) // Is timer event pending? { timeKillEvent(wTimerID); // Cancel the timer. wTimerID = 0; } } ///////////////////////////////////////////////////////////////////////////// // CJavuPlayerCtrl HRESULT CJavuPlayerCtrl::InitDirectShow() { // Zero interfaces (sanity check) pGB = NULL; pMS = NULL; pMP = NULL; pMC = NULL; pME = NULL; pBV = NULL; pVW = NULL; // DES pXml = NULL; pRender = NULL; pTimeline = NULL; pVideoTrack = NULL; pAudioTrack = NULL; return S_OK; } HRESULT CJavuPlayerCtrl::FreeDirectShow() { HRESULT hr=S_OK; // Hide video window and remove owner. This is not necessary here, // since we are about to destroy the filter graph, but it is included // for demonstration purposes. Remember to hide the video window and // clear its owner when destroying a window that plays video. if(pVW) { hr = pVW->put_Visible(OAFALSE); hr = pVW->put_Owner(NULL); } // Release all DirectShow objects used as global variables SAFE_RELEASE(pMC); SAFE_RELEASE(pMP); SAFE_RELEASE(pVW); SAFE_RELEASE(pBV); SAFE_RELEASE(pME); SAFE_RELEASE(pMS); SAFE_RELEASE(pGB); // DES SAFE_RELEASE(pXml); SAFE_RELEASE(pRender); SAFE_RELEASE(pTimeline); SAFE_RELEASE(pVideoTrack); SAFE_RELEASE(pAudioTrack); //#ifdef DEBUG_OUTPUT RemoveFromRot(m_dwRegister); //#endif return hr; } void CJavuPlayerCtrl::ResetDirectShow() { // Destroy the current filter graph its filters. FreeDirectShow(); // Reinitialize graph builder and query for interfaces InitDirectShow(); } /////////////////////////////////////////////////////////////////////////// // // Only works for video. For audio-only files, returns E_NOINTERFACE. // HRESULT CJavuPlayerCtrl::GetTotalDurationFrame(LONGLONG *pllTotalFrames) { // Is frame time format supported for this file? if (S_OK != pMS->IsFormatSupported(&TIME_FORMAT_FRAME)) { return E_NOINTERFACE; } // Read the time format to restore later GUID guidOriginalFormat; HRESULT hr = pMS->GetTimeFormat(&guidOriginalFormat); if (FAILED(hr)) return hr; // Set to frame time format hr = pMS->SetTimeFormat(&TIME_FORMAT_FRAME); if (FAILED(hr)) return hr; // Read the file's duration - get the total number of frames hr = pMS->GetDuration(pllTotalFrames); if (FAILED(hr)) return hr; // Return to the original format if (guidOriginalFormat != TIME_FORMAT_FRAME) { hr = pMS->SetTimeFormat(&guidOriginalFormat); if (FAILED(hr)) return hr; } return hr; } ///////////////////////////////////////////////////////////////////// // // Returns the result in *pllTotalDuration in 100-nanosecond units // (which TIME_FORMAT_MEDIA_TIME is measured in). // HRESULT CJavuPlayerCtrl::GetTotalDurationTime(REFERENCE_TIME *pDuration) { HRESULT hr; // Enforce the pre-condition if (NULL == pDuration) return E_INVALIDARG; // Is media time supported for this file? if (S_OK != pMS->IsFormatSupported(&TIME_FORMAT_MEDIA_TIME)) return E_NOINTERFACE; // Read the time format to restore later GUID guidOriginalFormat; hr = pMS->GetTimeFormat(&guidOriginalFormat); if (FAILED(hr)) return hr; // Set to time format for easy display hr = pMS->SetTimeFormat(&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) return hr; // Read the file's duration hr = pMS->GetDuration(pDuration); if (FAILED(hr)) return hr; // Return to the original format if (guidOriginalFormat != TIME_FORMAT_MEDIA_TIME) { hr = pMS->SetTimeFormat(&guidOriginalFormat); if (FAILED(hr)) return hr; } return hr; } ////////////////////////////////////////////////////////////////// // // Tries to force the current frame to (re)paint and waits for it to complete // // Times out after 500 milliseconds. // HRESULT CJavuPlayerCtrl::RenderCurrentFrame() { HRESULT hr = S_OK; pMC->Pause(); // // Wait for the stop to propagate to all filters // OAFilterState fs; // Condition: { You better not own a critical section with the main thread waiting for it, // because then you'll freeze. } hr = pMC->GetState(1000, &fs); return hr; } void CJavuPlayerCtrl::DisplayCurrentPosition() { static LONGLONG llOldPosition = 0; CString strCurrentPosition; REFERENCE_TIME rtCurrentPositionTime = 0; // Get current position (and update member variables) GetCurrentPosition (&rtCurrentPositionTime, NULL); #ifdef OFFSET_TIMECODE // Add to match the initial "02:55:02:13" time code on the fanchertest.mpg test film const long lOffsetTimecode = 2550213; REFERENCE_TIME rtOffsetTime = 0; Timecode2Time (lOffsetTimecode, &rtOffsetTime, m_dFrameRate); rtCurrentPositionTime += rtOffsetTime; #endif Time2Timecode (rtCurrentPositionTime, &strCurrentPosition, m_dFrameRate); // 100ns -> "hh:mm:ss:ff" or "hh:mm:ss" (if m_dFrameRate == 0) // Display current position ::SetWindowText (::GetDlgItem (m_hWnd, IDC_CURRENTPOSITION), strCurrentPosition); if (llOldPosition != rtCurrentPositionTime) { ::SendNotifyMessage (m_hWnd, WM_POSITIONCHANGED, m_playerState, (long)(rtCurrentPositionTime / 10000)); // 100ns -> ms // lParam is used to send the new position because wParam is an unsigned int, so it can't be used to send position in milliseconds (for which we need at least a long) // wParam is used for m_playerState, so that we can differentiate WM_POSITIONCHANGED messages sent during playback from all other ones (since during constantly sending messages during playback could be overwhelming for the host control, which might only want lightweight PositionChange notification) } llOldPosition = rtCurrentPositionTime; } ////////////////////////////////////////////////////////////////////// // // Pre-condition: { (current time format is TIME_FORMAT_MEDIA_TIME) /\ (FRAME_DURATION is valid) } // // Updates m_rtCurrentPositionTime and m_llCurrentPositionFrame // // Arguments prtCurrentPositionTime and pllCurrentPositionFrame may be NULL // HRESULT CJavuPlayerCtrl::GetCurrentPosition (REFERENCE_TIME *prtCurrentPositionTime, LONGLONG *pllCurrentPositionFrame) { REFERENCE_TIME currentPositionTime; LONGLONG currentPositionFrame; HRESULT hr = S_OK; if (!pMS) return E_POINTER; // // Assertion: { current time format is TIME_FORMAT_MEDIA_TIME } // // What's our current position? hr = pMS->GetCurrentPosition (¤tPositionTime); if (FAILED(hr)) return hr; // What frame is that? currentPositionFrame = FRAME_DURATION > 0 ? (LONGLONG)(currentPositionTime / FRAME_DURATION) : 0; // Update member variables m_rtCurrentPositionTime = currentPositionTime; m_llCurrentPositionFrame = currentPositionFrame; // Set return values if (prtCurrentPositionTime ) *prtCurrentPositionTime = currentPositionTime; if (pllCurrentPositionFrame) *pllCurrentPositionFrame = currentPositionFrame; if (FAILED(hr)) return hr; return hr; } /////////////////////////////////////////////////////////////////////////// // // Displays a text string in a status line near the bottom of the dialog // CJavuPlayerCtrl::DisplayStatus(LPCTSTR szText) { m_strStatus.Format (_T("%s"), szText); HWND hwndStatus = ::GetDlgItem (m_hWnd, IDC_STATUS); ::SetWindowText (hwndStatus, szText); } void CJavuPlayerCtrl::EnableGUI(bool bEnable) { ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PLAY), bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_STOP), bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PAUSE), bEnable); // if we can't figure out the frame rate or if it's audio only, then disable the following GUI controls { bool _bEnable = !m_bAudioOnly && m_rtAveTimePerFrame > 0 ? bEnable : false; ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTFORWARD), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTREVERSE), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_NEXTFRAME), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PREVIOUSFRAME), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_JUMP), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_CURRENTPOSITION), _bEnable); ::EnableWindow (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), bEnable); } } HRESULT CJavuPlayerCtrl::InitPlayer() { HRESULT hr = S_OK; // Initialize COM //CoInitialize(NULL); m_psCurrent = State_Stopped; m_bLooping = false; m_bMute = false; m_hPlayFastThread = NULL; m_rtStopPositionTime = m_llStopPositionFrame = 0; // Initialize the seek bar slider ::SendMessage (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), TBM_SETRANGE, TRUE, MAKELONG(0, TICKLEN)); m_wSeekSliderTimerID = 0; // Zero DirectShow interfaces (sanity check) pGB = NULL; pMS = NULL; pMP = NULL; pMC = NULL; pME = NULL; pBV = NULL; pVW = NULL; // Added by Alexz for timeline playing pXml = NULL; pRender = NULL; pTimeline = NULL; // Added by Alexz for timeline playing pAudioTrack = NULL; pVideoTrack = NULL; // // GUI stuff // m_Screen = ::GetDlgItem (m_hWnd, IDC_MOVIE_SCREEN); // What's visible and what's hidden? ShowSlider (m_bShowSlider); ShowControls (m_bShowControls); ShowStatusBar (m_bShowStatusBar); // Put pictures on the buttons AddIconToButton (IDC_BUTTON_PLAY, IDI_ICON_PLAY); AddIconToButton (IDC_BUTTON_PAUSE, IDI_ICON_PAUSE); AddIconToButton (IDC_BUTTON_STOP, IDI_ICON_STOP); AddIconToButton (IDC_BUTTON_NEXTFRAME, IDI_ICON_NEXTFRAME); AddIconToButton (IDC_BUTTON_PREVIOUSFRAME, IDI_ICON_PREVIOUSFRAME); AddIconToButton (IDC_BUTTON_FASTFORWARD, IDI_ICON_FASTFORWARD); AddIconToButton (IDC_BUTTON_FASTREVERSE, IDI_ICON_FASTREVERSE); AddIconToButton (IDC_BUTTON_JUMP, IDI_ICON_JUMP); m_playerState = PlayerState_Uninitialized; // If the filename property was set during design-time, load the file if ( ! m_strFilename.IsEmpty()) { hr = LoadFile (m_strFilename); } else { if (m_bStateChangeNotification) Fire_StateChange(PlayerState_Uninitialized, PlayerState_Uninitialized); } // If a media file was specified and the playback rate was set, play the file if (( ! m_strFilename.IsEmpty()) && (m_dPlaybackRate != 0)) { hr = Play (m_dPlaybackRate); } return hr; } void CJavuPlayerCtrl::FreePlayer() { try { // don't want to get those buggy errors when we quit IE: "instruction could not be read" StopTimer (m_wSeekSliderTimerID); StopPlayFastThread(); Sleep (500); StopMedia(); // Wait for the stop to propagate to all filters OAFilterState fs; pMC->GetState(500, &fs); // Release DirectShow interfaces FreeDirectShow(); // Release COM //CoUninitialize(); } catch (...) { StopTimer (m_wSeekSliderTimerID); #ifdef DEBUG_OUTPUT MessageBox (_T("caught an exception"), _T("SynchMoveSlider()")); #endif } } ///////////////////////////////////////////////////////////////////// // // Repositions to the beginning or middle of the current frame // (depending on whether the SEEK_INTO_MIDDLE_OF_FRAME preprocessor variable is defined) // HRESULT CJavuPlayerCtrl::NormalizeCurrentPosition() { if (FRAME_DURATION <= 0) return E_FAIL; HRESULT hr; // What frame are we on? m_llCurrentPositionFrame = (LONGLONG)(m_rtCurrentPositionTime / FRAME_DURATION); //if (currentPosition % FRAME_DURATION) currentPositionFrame++; // Recalculate current position m_rtCurrentPositionTime = (REFERENCE_TIME)(m_llCurrentPositionFrame * FRAME_DURATION); #ifdef SEEK_INTO_MIDDLE_OF_FRAME m_rtCurrentPositionTime += (REFERENCE_TIME)(FRAME_DURATION / 2); #endif // Reposition to the middle of the frame hr = pMS->SetPositions (&m_rtCurrentPositionTime, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); if (FAILED(hr)) return hr; return hr; } HRESULT CJavuPlayerCtrl::GetCurrentPositionFrame(LONGLONG *pllFrame) { HRESULT hr = S_OK; hr = pMS->SetTimeFormat (&TIME_FORMAT_FRAME); if (FAILED(hr)) { pMS->SetTimeFormat (&TIME_FORMAT_MEDIA_TIME); return hr; } hr = pMS->GetPositions (pllFrame, NULL); if (FAILED(hr)) { return hr; } hr = pMS->SetTimeFormat (&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) { return hr; } return hr; } void CJavuPlayerCtrl::ShowControls(bool bShow) { m_bShowControls = bShow; int nCmdShow = (bShow ? SW_SHOWNORMAL : SW_HIDE); // show/hide buttons and edit controls ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PLAY), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_STOP), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PAUSE), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTFORWARD), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTREVERSE), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_NEXTFRAME), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PREVIOUSFRAME), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_JUMP), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_CURRENTPOSITION), nCmdShow); // Resize the screen and reposition the controls RepositionControls(); } void CJavuPlayerCtrl::ShowStatusBar(bool bShow) { m_bShowStatusBar = bShow; int nCmdShow = (bShow ? SW_SHOWNORMAL : SW_HIDE); // show/hide status and frame rate displays ::ShowWindow (::GetDlgItem (m_hWnd, IDC_STATUS), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_FRAMERATE), nCmdShow); ::ShowWindow (::GetDlgItem (m_hWnd, IDC_VERSION), nCmdShow); // Resize the screen and reposition the controls RepositionControls(); } void CJavuPlayerCtrl::ShowSlider(bool bShow) { m_bShowSlider = bShow; int nCmdShow = (bShow ? SW_SHOWNORMAL : SW_HIDE); // show/hide slider ::ShowWindow (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), nCmdShow); // Resize the screen and reposition the controls RepositionControls(); } void CJavuPlayerCtrl::RepositionControls() { // Resize the screen ResizeScreen(); // Reposition the controls int nScreenHeight, nSliderHeight, nControlsHeight; GetGUISize (&nScreenHeight, &nSliderHeight, &nControlsHeight, NULL); if (m_bShowSlider) MoveSlider (nScreenHeight); // position the slider right below the screen if (m_bShowControls) MoveControls (nScreenHeight + (m_bShowSlider ? nSliderHeight : 0)); if (m_bShowStatusBar) MoveStatusBar (nScreenHeight + (m_bShowSlider ? nSliderHeight : 0) + (m_bShowControls ? nControlsHeight : 0)); // Repaint everything, so that the controls won't leave smudges of themselves if they're moved RECT rect; rect.top = rect.left = 0; rect.bottom = m_sizeExtent.cy; rect.right = m_sizeExtent.cx; InvalidateRect (&rect); // Repaint the video screen just in case repaint(); } void CJavuPlayerCtrl::GetGUISize(int *pnScreenHeight, int *pnSliderHeight, int *pnControlsHeight, int *pnStatusbarHeight) { RECT rect; if (pnScreenHeight) { // What height is the screen? RECT rect; ::GetClientRect (::GetDlgItem (m_hWnd, IDC_MOVIE_SCREEN), &rect); *pnScreenHeight = rect.bottom + GUI_PADDING; } if (pnSliderHeight) { // What height is the slider? ::GetClientRect (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), &rect); *pnSliderHeight = rect.bottom + GUI_PADDING; } if (pnControlsHeight) { // What height is the play button? ::GetClientRect (::GetDlgItem (m_hWnd, IDC_BUTTON_PLAY), &rect); *pnControlsHeight = rect.bottom + 2 * GUI_PADDING; } if (pnStatusbarHeight) { // What height is the status display? ::GetClientRect (::GetDlgItem (m_hWnd, IDC_STATUS), &rect); *pnStatusbarHeight = rect.bottom + 2 * GUI_PADDING; } } SIZEL * CJavuPlayerCtrl::ResizeScreen(SIZEL *pScreenSize) { // What size should the screen be? int nSliderHeight, nControlsHeight, nStatusbarHeight; GetGUISize (NULL, &nSliderHeight, &nControlsHeight, &nStatusbarHeight); int GUIHeight = (m_bShowSlider ? nSliderHeight : 0) + (m_bShowControls ? nControlsHeight : 0) + (m_bShowStatusBar ? nStatusbarHeight : 0); // Resize and position the screen RECT rectParentWindow, rectMovieScreen; ::GetClientRect (m_hWnd, &rectParentWindow); ::GetClientRect (::GetDlgItem (m_hWnd, IDC_MOVIE_SCREEN), &rectMovieScreen); m_lScreenWidth = rectMovieScreen.right; m_lScreenHeight = rectParentWindow.bottom - GUIHeight; ::MoveWindow (::GetDlgItem (m_hWnd, IDC_MOVIE_SCREEN), 0, 0, m_lScreenWidth, m_lScreenHeight, TRUE); // Reposition DirectShow's video window inside our newly resized movie screen CenterVideo(); // return the new screen size if (pScreenSize) { pScreenSize->cx = m_lScreenWidth; pScreenSize->cy = m_lScreenHeight; } return pScreenSize; } void CJavuPlayerCtrl::MoveControls(int y) { // Move buttons and edit controls RECT rect; // > GetCoords (m_hWnd, IDC_BUTTON_PLAY, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PLAY), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // || GetCoords (m_hWnd, IDC_BUTTON_PAUSE, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PAUSE), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // | GetCoords (m_hWnd, IDC_BUTTON_STOP, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_STOP), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // << GetCoords (m_hWnd, IDC_BUTTON_FASTREVERSE, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTREVERSE), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // -1 GetCoords (m_hWnd, IDC_BUTTON_PREVIOUSFRAME, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_PREVIOUSFRAME), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // +1 GetCoords (m_hWnd, IDC_BUTTON_NEXTFRAME, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_NEXTFRAME), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // >> GetCoords (m_hWnd, IDC_BUTTON_FASTFORWARD, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_FASTFORWARD), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // timecode edit control GetCoords (m_hWnd, IDC_CURRENTPOSITION, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_CURRENTPOSITION), rect.left, y+2, rect.right - rect.left, rect.bottom - rect.top, TRUE); // goto button GetCoords (m_hWnd, IDC_BUTTON_JUMP, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_BUTTON_JUMP), rect.left, y+1, rect.right - rect.left, rect.bottom - rect.top, TRUE); } void CJavuPlayerCtrl::MoveSlider(int y) { RECT rect; // slider GetCoords (m_hWnd, IDC_SLIDER_SEEK, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); } void CJavuPlayerCtrl::MoveStatusBar(int y) { RECT rect; // status GetCoords (m_hWnd, IDC_STATUS, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_STATUS), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); // frame rate GetCoords (m_hWnd, IDC_FRAMERATE, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_FRAMERATE), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); /* // total duration GetCoords (m_hWnd, IDC_TOTALDURATION, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_TOTALDURATION), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); */ // version # GetCoords (m_hWnd, IDC_VERSION, &rect); ::MoveWindow (::GetDlgItem (m_hWnd, IDC_VERSION), rect.left, y, rect.right - rect.left, rect.bottom - rect.top, TRUE); } ///////////////////////////////////////////////////////////////// // // Saves lpszXMLString to a temp file called TEMP_XML_FILENAME // and calls LoadFile(); then deletes the temp file // HRESULT CJavuPlayerCtrl::LoadXMLString(LPCWSTR lpszXMLString) { HRESULT hr; char *pXMLString = (char *) malloc (wcslen (lpszXMLString) + 1); Wide2Ascii (lpszXMLString, pXMLString); // Write the XML string to a file HANDLE hFile = ::CreateFile (TEMP_XML_FILENAME, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL); if (hFile != INVALID_HANDLE_VALUE) { // Attach a CFile object to it. CFile file ((int) hFile); file.SetFilePath (TEMP_XML_FILENAME); file.Write (pXMLString, strlen (pXMLString)); file.Flush(); file.Close(); } else { return E_FAIL; } hr = LoadFile (TEMP_XML_FILENAME); ::DeleteFile (TEMP_XML_FILENAME); free (pXMLString); return hr; } HRESULT CJavuPlayerCtrl::InitVideoWindow(IVideoWindow *pVW) { HRESULT hr; if (( ! pVW) || ( ! pME)) return E_POINTER; m_bAudioOnly = false; // We'll manually set the video to be visible, so disable autoshow hr = pVW->put_AutoShow(OAFALSE); // Set the message drain of the video window to point to our main // application window. // // If this is an audio-only or MIDI file, then put_MessageDrain will fail. // hr = pVW->put_MessageDrain((OAHWND) m_hWnd); if (FAILED(hr)) { m_bAudioOnly = true; } else { m_bAudioOnly = false; } if (!m_bAudioOnly) { hr = pVW->put_Owner((OAHWND) m_Screen); hr = pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); } // Have the graph signal event via window callbacks hr = pME->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0); // Place video window within the bounding rectangle CenterVideo(); // Make the video window visible within the screen window. // If this is an audio-only file, then there won't be a video interface. if (!m_bAudioOnly) { hr = pVW->put_Visible(OATRUE); ::SendMessage (m_Screen, WM_PAINT, NULL, NULL); hr = pVW->SetWindowForeground(-1); } return hr; } HRESULT CJavuPlayerCtrl::InitDES(double dFrameRate) { // try to make sure the arguments are valid if (dFrameRate <= 0) return E_INVALIDARG; HRESULT hr; EnableGUI (false); m_dFrameRate = dFrameRate; DisplayFrameRate (m_dFrameRate); ResetDirectShow(); // create a timeline hr = CoCreateInstance(__uuidof(AMTimeline), NULL, CLSCTX_INPROC_SERVER, __uuidof(IAMTimeline), (void**)&pTimeline); //------------------------------------------------------------- // CREATE THE FOLLOWING (illustrated using XTL): // // // // // // // // // // // // // // // // Video // // GROUP: Add a video group to the timeline. IAMTimelineGroup *pVideoGroup = NULL; IAMTimelineObj *pVideoGroupObj = NULL; pTimeline->CreateEmptyNode(&pVideoGroupObj, TIMELINE_MAJOR_TYPE_GROUP); pVideoGroupObj->QueryInterface(__uuidof(IAMTimelineGroup), (void **)&pVideoGroup); // Set the group media type. AM_MEDIA_TYPE mtVideoGroup; ZeroMemory(&mtVideoGroup, sizeof(AM_MEDIA_TYPE)); mtVideoGroup.majortype = MEDIATYPE_Video; pVideoGroup->SetMediaType (&mtVideoGroup); pVideoGroup->SetOutputFPS (dFrameRate); pTimeline->AddGroup (pVideoGroupObj); pVideoGroupObj->Release(); // TRACK: Add a track to the video group. IAMTimelineObj *pVideoTrackObj = NULL; IAMTimelineComp *pVideoComp = NULL; pTimeline->CreateEmptyNode(&pVideoTrackObj, TIMELINE_MAJOR_TYPE_TRACK); pVideoGroup->QueryInterface(__uuidof(IAMTimelineComp), (void **)&pVideoComp); pVideoComp->VTrackInsBefore(pVideoTrackObj, 0); pVideoTrackObj->QueryInterface(__uuidof(IAMTimelineTrack), (void **)&pVideoTrack); SAFE_RELEASE(pVideoTrackObj); SAFE_RELEASE(pVideoComp); SAFE_RELEASE(pVideoGroup); // // Audio // // GROUP: Add an audio group to the timeline. IAMTimelineGroup *pAudioGroup = NULL; IAMTimelineObj *pAudioGroupObj = NULL; pTimeline->CreateEmptyNode(&pAudioGroupObj, TIMELINE_MAJOR_TYPE_GROUP); pAudioGroupObj->QueryInterface(__uuidof(IAMTimelineGroup), (void **)&pAudioGroup); // Set the group media type. AM_MEDIA_TYPE mtAudioGroup; ZeroMemory(&mtAudioGroup, sizeof(AM_MEDIA_TYPE)); mtAudioGroup.majortype = MEDIATYPE_Audio; pAudioGroup->SetMediaType (&mtAudioGroup); pAudioGroup->SetOutputFPS (dFrameRate); pTimeline->AddGroup (pAudioGroupObj); pAudioGroupObj->Release(); // TRACK: Add a track to the audio group. IAMTimelineObj *pAudioTrackObj = NULL; IAMTimelineComp *pAudioComp = NULL; pTimeline->CreateEmptyNode(&pAudioTrackObj, TIMELINE_MAJOR_TYPE_TRACK); pAudioGroup->QueryInterface(__uuidof(IAMTimelineComp), (void **)&pAudioComp); pAudioComp->VTrackInsBefore(pAudioTrackObj, 0); pAudioTrackObj->QueryInterface(__uuidof(IAMTimelineTrack), (void **)&pAudioTrack); SAFE_RELEASE(pAudioTrackObj); SAFE_RELEASE(pAudioComp); SAFE_RELEASE(pAudioGroup); // done with creating groups and tracks (both video and audio) //------------------------------------------------------------- // // create a rendering engine // if (SUCCEEDED(hr)) hr = CoCreateInstance(__uuidof(RenderEngine), NULL, CLSCTX_INPROC_SERVER, __uuidof(IRenderEngine), (void**) &pRender); if (SUCCEEDED(hr)) hr = pRender->SetTimelineObject(pTimeline); hr = InitGraph(); hr = InitVideoWindow (pVW); return hr; } /////////////////////////////////////////////////////////////////////////////////////////////////// // // Builds the graph and gets the interfaces. // // Pre-condition: { InitDES() was already called to create the timeline and the rendering engine } // HRESULT CJavuPlayerCtrl::InitGraph() { HRESULT hr; // enforce the pre-condition if (( ! pRender) || ( ! pTimeline)) return E_POINTER; // Just in case... SAFE_RELEASE (pGB); SAFE_RELEASE (pMC); SAFE_RELEASE (pMS); SAFE_RELEASE (pMP); SAFE_RELEASE (pBV); SAFE_RELEASE (pVW); SAFE_RELEASE (pME); // // build the graph // hr = pRender->ConnectFrontEnd(); if (SUCCEEDED(hr)) hr = pRender->RenderOutputPins(); if (SUCCEEDED(hr)) hr = pRender->GetFilterGraph(&pGB); else return hr; // // get the interfaces // JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC)); // You should first run the filter graph for a while, for the engine to load all source and decode filters hr = pMC->Run(); hr = pMC->StopWhenReady(); JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS)); JIF(pGB->QueryInterface(IID_IMediaPosition, (void **)&pMP)); JIF(pGB->QueryInterface(IID_IBasicVideo, (void **)&pBV)); JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW)); JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME)); hr = pMS->SetTimeFormat (&TIME_FORMAT_MEDIA_TIME); return hr; CLEANUP: FreeDirectShow(); return(hr); } ////////////////////////////////////////////////////////////////////////////////// // // Permanently removes this source (audio and/or video) from the timeline, // including subobjects and children. // HRESULT CJavuPlayerCtrl::RemoveSource(REFERENCE_TIME *pInOut) { // make sure DES is initialized if ( ! (pVideoTrack && pAudioTrack)) return E_POINTER; // try to make sure the arguments are valid if ( ! pInOut) return E_INVALIDARG; HRESULT hr; EnableGUI (false); IAMTimelineObj *pSource; const REFERENCE_TIME in = *pInOut; // remove the audio source pSource = NULL; *pInOut = in; if (S_OK == (hr = pAudioTrack->GetNextSrc (&pSource, pInOut))) { hr = pSource->RemoveAll(); SAFE_RELEASE(pSource); } // remove the video source pSource = NULL; *pInOut = in; if (S_OK == (hr = pVideoTrack->GetNextSrc (&pSource, pInOut))) { hr = pSource->RemoveAll(); SAFE_RELEASE(pSource); } hr = BuildDESGraph(); if (SUCCEEDED(hr)) hr = CalculateTotalDuration(); if (SUCCEEDED(hr)) hr = JumpToSlider(); // when the timeline is rebuilt, reseek to the same slider position (which will be a difference position on the timeline) EnableGUI (true); return hr; } ////////////////////////////////////////////////////////////////////////////////////// // // Pre-condition: { InitDES() was already called to create the timeline, groups, and // tracks (both video and audio). } // // NOTE: if adding more than 75 sources, remove the first if-then statement below that returns E_POINTER // and read the DirectShow DES documentation: IAMTimelineTrack::SrcAdd (read the Remarks section). // HRESULT CJavuPlayerCtrl::AddSource(LPCWSTR lpszFilename, LONGLONG in, LONGLONG out) { // make sure DES is initialized if ( ! pTimeline || ! pVideoTrack || ! pAudioTrack) return E_POINTER; // if adding more than 75 source, read read the DirectShow DES documentation: IAMTimelineTrack::SrcAdd (read the Remarks section) long lNumberOfSources; if (pVideoTrack->GetSourcesCount (&lNumberOfSources) > 75) return E_POINTER; // try to make sure the arguments are valid if ( ! (lpszFilename && IsValidFile (lpszFilename) && (in < out)) ) { return E_INVALIDARG; } HRESULT hr; player_state_t oldPlayerState = m_playerState; EnableGUI (false); //--------------------------------------------------------------------------------- // Add a video and audio source // // Video // // SOURCE: Add a video source to the video track. IAMTimelineSrc *pVideoSource = NULL; IAMTimelineObj *pVideoSourceObj = NULL; hr = pTimeline->CreateEmptyNode(&pVideoSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pVideoSourceObj->QueryInterface(__uuidof(IAMTimelineSrc), (void **)&pVideoSource); // Set the times and the file name. LONGLONG timeline_totalDurationTime, timeline_start, timeline_stop; hr = GetTotalDurationTime (&timeline_totalDurationTime); if (SUCCEEDED(hr) || (timeline_totalDurationTime > 0)) { timeline_start = timeline_totalDurationTime; timeline_stop = timeline_totalDurationTime + (out - in); } else { timeline_start = 0; timeline_stop = out - in; } pVideoSourceObj->SetStartStop (timeline_start, timeline_stop); pVideoSource->SetMediaName ((LPWSTR)lpszFilename); pVideoSource->SetMediaTimes (in, out); pVideoTrack->SrcAdd(pVideoSourceObj); SAFE_RELEASE (pVideoSourceObj); SAFE_RELEASE (pVideoSource); // // Audio // // SOURCE: Add a video source to the video track. IAMTimelineSrc *pAudioSource = NULL; IAMTimelineObj *pAudioSourceObj = NULL; hr = pTimeline->CreateEmptyNode(&pAudioSourceObj, TIMELINE_MAJOR_TYPE_SOURCE); hr = pAudioSourceObj->QueryInterface(__uuidof(IAMTimelineSrc), (void **)&pAudioSource); // Set the times and the file name - use the same once as were used for the video source pAudioSourceObj->SetStartStop (timeline_start, timeline_stop); pAudioSource->SetMediaName ((LPWSTR)lpszFilename); pAudioSource->SetMediaTimes (in, out); pAudioTrack->SrcAdd(pAudioSourceObj); SAFE_RELEASE (pAudioSourceObj); SAFE_RELEASE (pAudioSource); // done adding the sources //--------------------------------------------------------------------------------- if (SUCCEEDED(hr)) BuildDESGraph(); hr = CalculateTotalDuration(); if (FAILED(hr)) { FreeDirectShow(); DisplayStatus(TEXT("Load failed! (unknown duration)")); m_playerState = PlayerState_LoadFailed; if (m_bStateChangeNotification && (oldPlayerState != m_playerState)) Fire_StateChange(oldPlayerState, m_playerState); return hr; } if (SUCCEEDED(hr)) hr = JumpToSlider(); // when the timeline is rebuilt, reseek to the same slider position (which will be a difference position on the timeline) EnableGUI (true); return hr; } HRESULT CJavuPlayerCtrl::DisplayFrameRate(double dFrameRate) { // // Display the frame rate // CString strFrameRate; if (m_bAudioOnly) { // audio only - frame rate is meaningless strFrameRate.Format (_T("")); } else { // video if (m_dFrameRate > 0) { strFrameRate.Format (_T("%.2f fps"), dFrameRate); } else { strFrameRate.Format (_T("? fps")); } } ::SetWindowText (::GetDlgItem (m_hWnd, IDC_FRAMERATE), strFrameRate); return S_OK; } ///////////////////////////////////////////////////////////////////////////////// // // For now, this method doesn't really do anything other than serve as a sample // in case we will need to locate a specific source. // (Another option is to just keep the source clip information ourselves, // so we wouldn't have to ask the track). // // To remove a source from the timeline (from a track actually), // you would have to find the right source object on both the audio and video tracks // and then all IAMTimelineObj::RemoveAll // To move a source to another place on the timeline, maybe you could use Remove // HRESULT CJavuPlayerCtrl::EnumSources (IAMTimelineTrack *pTrack) { if ( ! pTrack) return E_POINTER; HRESULT hr; REFERENCE_TIME rtInOut = 0; IAMTimelineObj *pSourceObject = NULL; IAMTimelineSrc *pSource = NULL; REFERENCE_TIME rtStart, rtStop, // relative to the timeline rtIn, rtOut; // relative to the source BSTR *pFilename = NULL; // memory for this string will be allocated by the IAMTimelineSrc::GetMedianame() method. // how many source does the track contain? long lNumberOfSources; hr = pTrack->GetSourcesCount (&lNumberOfSources); // enumerate all the sources on the track while (S_OK == pTrack->GetNextSrc (&pSourceObject, &rtInOut)) { hr = pSourceObject->GetStartStop (&rtStart, &rtStop); // what are the start and stop times relative to the timeline? hr = pSourceObject->QueryInterface(__uuidof(IAMTimelineSrc), (void **)&pSource); if (SUCCEEDED(hr)) hr = pSource->GetMediaTimes (&rtIn, &rtOut); // what are the in and out points relative to the source? if (SUCCEEDED(hr)) hr = pSource->GetMediaName (pFilename); // what's the filename of the source? // cleanup SysFreeString (*pFilename); SAFE_RELEASE (pSource); SAFE_RELEASE (pSourceObject); } return S_OK; } //////////////////////////////////////////////////////////////////////////////////////////// // // Used by ::RemoveAudioRenderingFitler() // // 1. Stops the filter and disconnects its INPUT pin and the OUTPUT pin of the filter to which it's connected, // 2. Removes the filter from filter graph; calls pGB->RemoveFilter(). // HRESULT CJavuPlayerCtrl::RemoveFilter(IBaseFilter *pFilter) { HRESULT hr; // Enforce the pre-condition if ( ! pFilter) return E_POINTER; // Stop the filter. hr = pFilter->Stop(); if (FAILED(hr)) return hr; // Disconnects its INPUT pin and the OUTPUT pin of the filter to which it's connected. IPin *pPinIn; GetPin (pFilter, PINDIR_INPUT, &pPinIn); IPin *pPinOut = NULL; hr = pPinIn->ConnectedTo (&pPinOut); if (FAILED(hr)) return hr; hr = pGB->Disconnect (pPinIn); if (FAILED(hr)) return hr; hr = pGB->Disconnect (pPinOut); if (FAILED(hr)) return hr; // Remove the filter from the filter graph. hr = pGB->RemoveFilter (pFilter); if (FAILED(hr)) return E_FAIL; #ifdef DEBUG_OUTPUT MessageBox (_T("Audio filter removed")); #endif return hr; } /////////////////////////////////////////////////////////////// // // Returns the found PINDIR_OUTPUT IPin pointer in *pPinFound // (pass in the address of the pointer) // Returns NULL if found nothing. // HRESULT CJavuPlayerCtrl::GetPin(IBaseFilter *pFilter, PIN_DIRECTION pinDir, IPin **ppPinFound) { *ppPinFound = NULL; HRESULT hr; IEnumPins *pEnum = NULL; IPin *pPin = NULL; // Get pin enumerator hr = pFilter->EnumPins(&pEnum); assert (SUCCEEDED(hr)); if (FAILED(hr)) { return hr; } // Enumerate all pins on this filter while(pEnum->Next(1, &pPin, 0) == S_OK) { PIN_DIRECTION PinDirThis; hr = pPin->QueryDirection(&PinDirThis); if (FAILED(hr)) { pPin->Release(); continue; } // Does the pin's direction match the requested direction? if (pinDir == PinDirThis) { *ppPinFound = pPin; } pPin->Release(); } pEnum->Release(); return hr; } ///////////////////////////////////////////////////////////////////////////////////////////// // // 1. Creates pAudioRenderingFilter. // 2. Adds it to the graph and names it AUDIO_RENDERING_FILTER_NAME. // 3. Connects its INPUT pin directly to pAudioDecoderFilter's OUTPUT pin. // // Use ::RemoveAudioRenderingFilter() to disconnect and remove the audio rendering filter from the graph. // HRESULT CJavuPlayerCtrl::AddAudioRenderingFilter(IBaseFilter **ppAudioRenderingFilter, LPCWSTR pAudioRenderingFilterName, IBaseFilter *pAudioDecoderFilter) { HRESULT hr; if ( ! m_bAudioRenderingFilterRemoved) return E_FAIL; // it seems there is one already there! if ( ! m_pAudioDecoderFilter) return E_POINTER; // // Create an audio rendering filter // *ppAudioRenderingFilter = NULL; CoCreateInstance (CLSID_DSoundRender, //DSDEVID_DefaultPlayback NULL, CLSCTX_INPROC_SERVER, //CLSCTX_INPROC // what the hell is this? IID_IBaseFilter, (void **)ppAudioRenderingFilter); if ( ! m_pAudioRenderingFilter) return E_FAIL; StopMedia(); // For some reason, video (only MPEG?) glitches by skipping a tiny bit further, when going from FastFoward to Play // // Add the audio rendering filter to the graph // hr = pGB->AddFilter (*ppAudioRenderingFilter, pAudioRenderingFilterName); if (FAILED(hr)) return hr; // // Connects the audio rendering filter's INPUT pin directly to m_pAudioDecoderFilter's OUTPUT pin. // IPin *pPinOut = NULL; hr = GetPin (pAudioDecoderFilter, PINDIR_OUTPUT, &pPinOut); if (FAILED(hr)) return hr; IPin *pPinIn = NULL; hr = GetPin (*ppAudioRenderingFilter, PINDIR_INPUT, &pPinIn); if (FAILED(hr)) return hr; // // Assertion: { pPinOut and pPinIn are both disconnected } // pGB->ConnectDirect (pPinOut, pPinIn, NULL); if (FAILED(hr)) return hr; m_bAudioRenderingFilterRemoved = false; #ifdef DEBUG_OUTPUT MessageBox (_T("An audio rendering filter added")); #endif return hr; } ///////////////////////////////////////////////////////////////////////////////////////////// // // Use CoCreateInstance() and ::AddAudioRenderingFilter() to add the sound rendering filter back into the graph. // // Used by ::OnFastFoward() to achieve playback with speeds greater than 2x. // // IMediaSeeking::SetRate() will not let set a rate higher than 2x for files that have an // audio track (because audio samples will NOT be dropped, but rather will be played back // at a higher pitch, so the limit there it seems is 2x). // So to fast foward faster than 2x, you would have to drop the audio (muting is NOT enough, // you'll still get a VFW_E_UNSUPPORTED_AUDIO error), which can be done by removing the // audio rendering device filter (e.g. DefaultDirectSoundDevice). Merely disconnecting // its pins doesn't seem to be enough. // HRESULT CJavuPlayerCtrl::RemoveAudioRenderingFitler() { HRESULT hr; // Make sure everything is stopped //OAFilterState state; //pMC->GetState (INFINITE, &state); //if (state != State_Stopped) return VFW_E_NOT_STOPPED; if (m_bAudioRenderingFilterRemoved) return S_FALSE; // It seems it might already be removed! else { // Save current position REFERENCE_TIME rtCurrentPosition; pMS->GetPositions (&rtCurrentPosition, NULL); // Stop the filter graph StopMedia(); // Remove the audio rendering filter #ifdef DEBUG_OUTPUT MessageBox (_T("About to remove the audio rendering filter")); #endif hr = RemoveFilter (m_pAudioRenderingFilter); if (SUCCEEDED(hr)) m_bAudioRenderingFilterRemoved = true; } return hr; } HRESULT CJavuPlayerCtrl::EnumFilters() { HRESULT hr; IEnumFilters *pEnum = NULL; IBaseFilter *pFilter = NULL; ULONG cFetched; m_pAudioDecoderFilter = m_pAudioRenderingFilter = NULL; m_bAudioRenderingFilterRemoved = true; // Get filter enumerator hr = pGB->EnumFilters(&pEnum); if (FAILED(hr)) return hr; // Enumerate all filters in the graph while(pEnum->Next(1, &pFilter, &cFetched) == S_OK) { TCHAR szName[256]; hr = GetFilterName (pFilter, szName); // If filter name contains "Audio" then assume it's an audio decoder // and save off its pointer so we can disconnect (for 10x FastFoward) and connect it back (for normal 1x playback with sound) if (strstr ((char *)szName /* assuming it's ASCII, and not UNICODE? */, "Audio Decoder")) { m_pAudioDecoderFilter = pFilter; } else if (strstr ((char *)szName, "Default DirectSound Device")) { m_pAudioRenderingFilter = pFilter; m_bAudioRenderingFilterRemoved = false; } pFilter->Release(); } pEnum->Release(); return hr; } HRESULT CJavuPlayerCtrl::GetFilterName(IBaseFilter *pFilter, TCHAR szName[]) { FILTER_INFO FilterInfo; HRESULT hr = pFilter->QueryFilterInfo(&FilterInfo); if (FAILED(hr)) return hr; else { WideCharToMultiByte(CP_ACP, 0, FilterInfo.achName, -1, (char *)szName, 256, 0, 0); FilterInfo.pGraph->Release(); } return hr; } HRESULT CJavuPlayerCtrl::HandleECComplete() { HRESULT hr; #ifdef DEBUG_OUTPUT GetLocalTime (&m_stPlaybackTimeFinish); // stop stopwatch long playbacktime = (m_stPlaybackTimeFinish.wHour * 60 + m_stPlaybackTimeFinish.wMinute * 60 + m_stPlaybackTimeFinish.wSecond) - (m_stPlaybackTimeStart.wHour * 60 + m_stPlaybackTimeStart.wMinute * 60 + m_stPlaybackTimeStart.wSecond); CString msg; msg.Format (_T("regular playback took %ld seconds"), playbacktime); MessageBox (msg); #endif // If looping, reset to beginning and continue playing if (m_bLooping) { LONGLONG pos=0; // Reset to first frame of movie hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning , NULL, AM_SEEKING_NoPositioning); if (FAILED(hr)) { // Some custom filters (like the Windows CE MIDI filter) // may not implement seeking interfaces (IMediaSeeking) // to allow seeking to the start. In that case, just stop // and restart for the same effect. This should not be // necessary in most cases. StopMedia(); RunMedia(); } } else { // Stop playback (and perhaps display first frame of movie) OnStop(m_bPositionToBeginningWhenReachedEnd); } if (m_bEndOfStreamNotification) Fire_EndOfStream(0); return hr; } ////////////////////////////////////////////////////////////////////////////////////// // // Attempt a jump to strTimecode and returns the new position in *pstrResultTimecode. // Fires an PositionChange event if the new position differs from the old one. // HRESULT CJavuPlayerCtrl::OnJump(CString strTimecode, CString *pstrResultTimecode) { if ((m_dFrameRate <= 0) || (FRAME_DURATION <= 0)) return E_FAIL; long lNewPositionTimecode; // "hh:mm:ss:ff" or "hhmmssff" -> hhmmssff TimecodeString2Int (strTimecode, &lNewPositionTimecode); OnPause(); // call this after you get the new position typed in by the user; ...because OnPause()->SynchMoveSlider()->DisplayCurrentPosition(), which will reset the contents of the IDC_CURRENTPOSITION edit control to the current position. // hhmmssff -> 100ns REFERENCE_TIME rtNewPositionTime; Timecode2Time (lNewPositionTimecode, &rtNewPositionTime, m_dFrameRate); #ifdef OFFSET_TIMECODE REFERENCE_TIME rtOffsetTime; Timecode2Time (2550213, &rtOffsetTime, m_dFrameRate); rtNewPositionTime -= rtOffsetTime; #endif HRESULT hr = JumpToFrame ((rtNewPositionTime + (LONGLONG)(FRAME_DURATION / 4)) / FRAME_DURATION); // + (pTimeline ? 1 : 0))); // <-- quick fix for an unknown bug! (if using DES then add one frame) // Return the timecode we actually jumped to since it might differ from the one requested. For example, if user requested to jump PAST the end of file, then we'll actually jump to the end of file. // 100ns -> "hh:mm:ss:ff" return hr; } ////////////////////////////////////////////////// // // Call after making changes to the DES timeline. // HRESULT CJavuPlayerCtrl::BuildDESGraph() { if (( ! pRender) || (! pMC)) return E_POINTER; HRESULT hr; // // (re)build the graph // hr = pRender->ConnectFrontEnd(); if (SUCCEEDED(hr)) hr = pRender->RenderOutputPins(); // You should first run the filter graph for a while, for the engine to load all source and decode filters if (SUCCEEDED(hr)) hr = pMC->Run(); if (SUCCEEDED(hr)) hr = pMC->StopWhenReady(); return hr; } HRESULT CJavuPlayerCtrl::CalculateTotalDuration() { HRESULT hr; // How long is the timeline (100ns)? hr = GetTotalDurationTime (&m_rtTotalDurationTime); if (SUCCEEDED(hr)) { // How many frames is that? m_rtAveTimePerFrame = (LONGLONG)(10000000 / m_dFrameRate); m_llTotalDurationFrame = (LONGLONG)(m_rtTotalDurationTime / m_rtAveTimePerFrame + (m_rtTotalDurationTime % m_rtAveTimePerFrame ? 1 : 0)); // m_llTotalDurationFrame = rounded up (total time duration / average time per frame) } return hr; } HRESULT CJavuPlayerCtrl::JumpToSlider() { HRESULT hr; DWORD dwSliderPosition = SendMessage(::GetDlgItem (m_hWnd, IDC_SLIDER_SEEK), TBM_GETPOS, 0, 0); // Seek to the new position if (m_rtAveTimePerFrame > 0) { // if we know the frame rate, go by frames LONGLONG newFrame = ((m_llTotalDurationFrame) * dwSliderPosition) / TICKLEN; //newFrame += (pTimeline ? 1 : 4); // <-- quick fix for an unknown bug! (if using DES then add one frame, otherwise add four frames) hr = JumpToFrame (newFrame); } else { // if we don't know the frame rate, go by time LONGLONG newTime = ((m_rtTotalDurationTime) * dwSliderPosition) / TICKLEN; hr = JumpToTime (newTime); } return hr; } //------------------------------------------------------------------------------------------------------ //-------------------------------- INTERFACE (IJavuPlayerCtrl) METHODS: -------------------------------- STDMETHODIMP CJavuPlayerCtrl::get_Rate(double *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_dPlaybackRate; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_Rate(double newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bInPlaceActive && m_bAudioOnly && ((newVal > AUDIO_PLAYBACK_RATE_MAX) || (newVal < 0))) { CString msg; msg.Format (_T("Playback rate for an audio-only file cannot exceed %.1f and must be greater than zero."), AUDIO_PLAYBACK_RATE_MAX); return Error(msg); } else if (ABS(newVal) > VIDEO_PLAYBACK_RATE_MAX) { CString msg; msg.Format (_T("Playback rate must be between -%.1f and %.1f"), VIDEO_PLAYBACK_RATE_MAX, VIDEO_PLAYBACK_RATE_MAX); return Error(msg); } else { if (m_bInPlaceActive) { // If it's run-time, try to load the file return Play (newVal); // if rate = 0, Play() should call Pause() } else // if ( ! m_bInPlaceActive) { // If it's design-time, just save the value m_dPlaybackRate = newVal; } } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_FileName(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_strFilename.AllocSysString(); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_FileName(BSTR newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) //MessageBox (newVal, _T("and the filename is...")); if (m_bInPlaceActive) { // If it's run-time, try to load the file HRESULT hr = LoadFile (newVal); if (FAILED(hr)) { CString msg; if (E_INVALIDARG == hr) msg.Format (_T("\"%s\" is not a valid media file."), newVal); else msg.Format (_T("File \"%s\" failed to render.\n\ Possible problems: codec for that media format is not installed; if trying to load an XML file, make sure it's valid, also make sure all source clip paths in the XML file are valid, and also that DirectX 8.0 or better is installed (run dxdiag to check DirectX version)."), newVal); return Error(msg); } } else // if ( ! m_bInPlaceActive) { // If it's design-time, just save the value m_strFilename = (LPCWSTR) newVal; } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_XMLString(BSTR newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bInPlaceActive) { // If it's run-time, try to load the file HRESULT hr = LoadXMLString (newVal); if (FAILED(hr)) { CString msg; if (E_INVALIDARG == hr) msg.Format (_T("\"%s\" is not a valid media file."), newVal); else msg.Format (_T("File \"%s\" failed to render.\n\ Most likely you don't have a codec installed that supports that media format."), newVal); return Error(msg); } } else // if ( ! m_bInPlaceActive) { // If it's design-time, just save the value m_strFilename = TEMP_XML_FILENAME; } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_FrameRate(double *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_dFrameRate; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::pause() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return Play(0); } STDMETHODIMP CJavuPlayerCtrl::play() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return Play(1.0); } STDMETHODIMP CJavuPlayerCtrl::stop() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return OnStop(); } STDMETHODIMP CJavuPlayerCtrl::fastForward() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return OnFastForward(); } STDMETHODIMP CJavuPlayerCtrl::fastReverse() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return OnFastReverse(); } STDMETHODIMP CJavuPlayerCtrl::nextFrame() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = OnPause(); if (FAILED(hr)) return hr; return NextFrame(1); } STDMETHODIMP CJavuPlayerCtrl::previousFrame() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = OnPause(); if (FAILED(hr)) return hr; return PreviousFrame(1); } STDMETHODIMP CJavuPlayerCtrl::get_TotalDurationTime(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) // Can't return m_rtTotalDuration as is, because there is a very big change of an overflow error *pVal = (long)(m_rtTotalDurationTime / 10000); // 100ns -> ms return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_TotalDurationFrame(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = (long) m_llTotalDurationFrame; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_TotalDurationString(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_dFrameRate <= 0) return E_FAIL; CString strTotalDuration; Time2Timecode (m_llTotalDurationFrame * FRAME_DURATION, &strTotalDuration, m_dFrameRate); // frame # -> "hh:mm:ss:ff" *pVal = strTotalDuration.AllocSysString(); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_StatusString(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_strStatus.AllocSysString(); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_CurrentPositionFrame(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = (long) m_llCurrentPositionFrame; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_CurrentPositionFrame(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bInPlaceActive) { JumpToFrame (newVal); } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_CurrentPositionTime(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = (long)(m_rtCurrentPositionTime / 10000); // 100ns -> ms return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_CurrentPositionTime(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bInPlaceActive) { JumpToTime ((LONGLONG)newVal * 10000); // ms -> 100ns } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_PreserveAspectRatio(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bPreserveAspectRatio; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_PreserveAspectRatio(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bPreserveAspectRatio = (newVal ? true : false); if (m_bInPlaceActive) { CenterVideo(); } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_CurrentPositionFrameTrue(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = S_OK; LONGLONG currentPositionFrame; hr = GetCurrentPositionFrame(¤tPositionFrame); if (FAILED(hr)) return hr; *pVal = (long) currentPositionFrame; return hr; } STDMETHODIMP CJavuPlayerCtrl::put_CurrentPositionFrameTrue(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = S_OK; LONGLONG currentPosition = newVal; hr = pMS->SetTimeFormat (&TIME_FORMAT_FRAME); if (FAILED(hr)) return hr; hr = pMS->SetPositions (¤tPosition, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning); if (FAILED(hr)) return hr; hr = pMS->SetTimeFormat (&TIME_FORMAT_MEDIA_TIME); if (FAILED(hr)) return hr; return hr; } STDMETHODIMP CJavuPlayerCtrl::repaint() { AFX_MANAGE_STATE(AfxGetStaticModuleState()) ::MoveWindow (m_Screen, 0, 0, m_lScreenWidth - 1, m_lScreenHeight - 1, TRUE); ::MoveWindow (m_Screen, 0, 0, m_lScreenWidth, m_lScreenHeight, TRUE); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_ShowControls(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bShowControls; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_ShowControls(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bShowControls = (newVal ? true : false); if (m_bInPlaceActive) // run-time { ShowControls (m_bShowControls); } else // design-time { FireViewChange(); // calls OnDraw() } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_ShowStatusBar(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bShowStatusBar; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_ShowStatusBar(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bShowStatusBar = (newVal ? true : false); if (m_bInPlaceActive) // run-time { ShowStatusBar (m_bShowStatusBar); } else // design-time { FireViewChange(); // calls OnDraw() } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_ShowTracker(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bShowSlider; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_ShowTracker(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bShowSlider = (newVal ? true : false); if (m_bInPlaceActive) // run-time { ShowSlider (m_bShowSlider); } else // design-time { FireViewChange(); // calls OnDraw() } return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_BitRate(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_lBitRate; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_CurrentPositionString(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_dFrameRate <= 0) return E_FAIL; CString strCurrentPosition; Time2Timecode (m_rtCurrentPositionTime, &strCurrentPosition, m_dFrameRate); // 100ns -> "hh:mm:ss:ff" or "hh:mm:ss" (if m_dFrameRate == 0) *pVal = strCurrentPosition.AllocSysString(); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_CurrentPositionString(BSTR newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bAudioOnly || m_dFrameRate <= 0) return E_FAIL; long lNewPositionTimecode; // "hh:mm:ss:ff" or "hhmmssff" -> hhmmssff TimecodeString2Int (newVal, &lNewPositionTimecode); //OnPause(); // call this after you get the new position typed in by the user; ...because OnPause()->SynchMoveSlider()->DisplayCurrentPosition(), which will reset the contents of the IDC_CURRENTPOSITION edit control to the current position. // hhmmssff -> 100ns REFERENCE_TIME rtNewPositionTime; Timecode2Time (lNewPositionTimecode, &rtNewPositionTime, m_dFrameRate); JumpToFrame ((LONGLONG)(rtNewPositionTime / FRAME_DURATION)); // + (pTimeline ? 1 : 0))); // <-- quick fix for an unknown bug! (if using DES then add one frame) return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_NewStreamNotification(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bNewStreamNotification; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_NewStreamNotification(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bNewStreamNotification = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_StateChangeNotification(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bStateChangeNotification; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_StateChangeNotification(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bStateChangeNotification = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_RateChangeNotification(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bRateChangeNotification; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_RateChangeNotification(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bRateChangeNotification = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_PositionChangeNotification(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bPositionChangeNotification; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_PositionChangeNotification(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bPositionChangeNotification = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_PositionChangeNotification_LightWeightOnly(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bPositionChangeNotification_LightWeightOnly; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_PositionChangeNotification_LightWeightOnly(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bPositionChangeNotification_LightWeightOnly = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_EndOfStreamNotification(VARIANT_BOOL *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_bEndOfStreamNotification; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_EndOfStreamNotification(VARIANT_BOOL newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) m_bEndOfStreamNotification = (newVal ? true : false); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::get_State(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = m_playerState; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::createTimeline(double frameRate) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) return InitDES (frameRate); } STDMETHODIMP CJavuPlayerCtrl::addClip(BSTR filename, long in, long out) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) CString msg; LONGLONG llIn = (LONGLONG)in * 10000, // ms -> 100ns llOut = (LONGLONG)out * 10000; // ms -> 100ns return AddSource (filename, llIn, llOut); } STDMETHODIMP CJavuPlayerCtrl::removeClip(long in) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) REFERENCE_TIME llInOut = (LONGLONG)in * 10000; // ms -> 100ns return RemoveSource (&llInOut); } STDMETHODIMP CJavuPlayerCtrl::get_StopPositionTime(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = (long)(m_rtStopPositionTime / 10000); // 100ns -> ms return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_StopPositionTime(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = S_OK; if (m_bInPlaceActive) { LONGLONG llStopTime = (LONGLONG)newVal * 10000; // ms -> 100ns hr = pMS->SetPositions (NULL, AM_SEEKING_NoPositioning, &llStopTime, AM_SEEKING_AbsolutePositioning); // update member variables if (SUCCEEDED(hr)) hr = pMS->GetStopPosition (&m_rtStopPositionTime); if (SUCCEEDED(hr)) m_llStopPositionFrame = m_rtStopPositionTime / FRAME_DURATION; // 100ns -> frame # } return hr; } STDMETHODIMP CJavuPlayerCtrl::get_StopPositionString(BSTR *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_dFrameRate <= 0) return E_FAIL; CString strStopPosition; Time2Timecode (m_llStopPositionFrame * FRAME_DURATION, &strStopPosition, m_dFrameRate); // frame # -> 100ns -> "hh:mm:ss:ff" *pVal = strStopPosition.AllocSysString(); return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_StopPositionString(BSTR newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) if (m_bAudioOnly || m_dFrameRate <= 0) return E_FAIL; long lNewPositionTimecode; // "hh:mm:ss:ff" or "hhmmssff" -> hhmmssff TimecodeString2Int (newVal, &lNewPositionTimecode); // hhmmssff -> 100ns REFERENCE_TIME rtNewPositionTime; Timecode2Time (lNewPositionTimecode, &rtNewPositionTime, m_dFrameRate); HRESULT hr = S_OK; if (m_bInPlaceActive) { LONGLONG llStopTime = (LONGLONG)newVal * 10000; // ms -> 100ns hr = pMS->SetPositions (NULL, AM_SEEKING_NoPositioning, &rtNewPositionTime, AM_SEEKING_AbsolutePositioning); // update member variables if (SUCCEEDED(hr)) hr = pMS->GetStopPosition (&m_rtStopPositionTime); if (SUCCEEDED(hr)) m_llStopPositionFrame = m_rtStopPositionTime / FRAME_DURATION; // 100ns -> frame # } return hr; } STDMETHODIMP CJavuPlayerCtrl::get_StopPositionFrame(long *pVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) *pVal = (long)m_llStopPositionFrame; return S_OK; } STDMETHODIMP CJavuPlayerCtrl::put_StopPositionFrame(long newVal) { AFX_MANAGE_STATE(AfxGetStaticModuleState()) HRESULT hr = S_OK; if (m_bInPlaceActive) { LONGLONG llStopTime = (LONGLONG)newVal * FRAME_DURATION; // frame # -> 100ns hr = pMS->SetPositions (NULL, AM_SEEKING_NoPositioning, &llStopTime, AM_SEEKING_AbsolutePositioning); // update member variables if (SUCCEEDED(hr)) hr = pMS->GetStopPosition (&m_rtStopPositionTime); if (SUCCEEDED(hr)) m_llStopPositionFrame = m_rtStopPositionTime / FRAME_DURATION; // 100ns -> frame # } return S_OK; }