////////////////////////////////////////////////////////////////////// // // // Copyright (c) 1998-2001 Microsoft Corporation // // // callnot.cpp // // Implementation of the ITTAPIEventNotification interface. // // This is an outgoing interface that is defined by TAPI 3.0. This // is basically a callback function that TAPI 3.0 calls to inform // the application of events related to calls (on a specific address) // // Please refer to COM documentation for information on outgoing // interfaces. // // An application must implement and register this interface in order // to receive calls and events related to calls // ////////////////////////////////////////////////////////////////////// #include #include #include #include #include "callnot.h" #include "FileTerm.h" #include "resource.h" extern ITBasicCallControl * g_pCall; extern HWND g_hDlg; extern ITTerminal *g_pPlayFileTerm; extern ITTerminal *g_pRecordFileTerm; // //to keep track of number of messages // DWORD g_dwMessages = 0; ////////////////////////////////////////////////////////////////// // // GetTerminalFromStreamEvent - get the stream from the event and // after this get the terminal from stream - if any // ////////////////////////////////////////////////////////////////// HRESULT GetTerminalFromStreamEvent( IN ITCallMediaEvent * pCallMediaEvent, OUT ITTerminal ** ppTerminal ) { // //do not return bogus value // *ppTerminal = NULL; // // Get the stream for this event. // ITStream * pStream = NULL; HRESULT hr = pCallMediaEvent->get_Stream( &pStream ); if (FAILED(hr)) { DoMessage( _T("GetTerminalFromStreamEvent: get_Stream failed")); return E_FAIL; } // // Enumerate the terminals on this stream. // IEnumTerminal * pEnumTerminal = NULL; hr = pStream->EnumerateTerminals( &pEnumTerminal ); // //not need this anymore // pStream->Release(); if ( FAILED(hr) ) { DoMessage(_T("GetTerminalFromStreamEvent: EnumerateTerminals failed")); return hr; } // // Get the first terminal -- if there aren't any, return E_FAIL so that // we skip this event (this happens when the terminal is on the other // stream). // hr = pEnumTerminal->Next(1, ppTerminal, NULL); // //clean up // pEnumTerminal->Release(); // //map anything but S_OK to failure - even S_FALSE // if ( hr != S_OK ) { return E_FAIL; } return S_OK; } /////////////////////////////////////////////////////////////////// // CallEventNotification // // The only method in the ITCallEventNotification interface. This gets // called by TAPI 3.0 when there is a call event to report. This just // posts the message to our UI thread, so that we do as little as // possible on TAPI's callback thread. // /////////////////////////////////////////////////////////////////// HRESULT STDMETHODCALLTYPE CTAPIEventNotification::Event( IN TAPI_EVENT TapiEvent, IN IDispatch * pEvent ) { // //sanity check // if(NULL == pEvent) { return S_OK; } // // Addref the event so it doesn't go away. // pEvent->AddRef(); // // Post a message to our own UI thread. // PostMessage( g_hDlg, WM_PRIVATETAPIEVENT, (WPARAM) TapiEvent, (LPARAM) pEvent ); return S_OK; } /////////////////////////////////////////////////////////////////// // OnTapiEvent // // This is the real event handler, called on our UI thread when // the WM_PRIVATETAPIEVENT message is received // /////////////////////////////////////////////////////////////////// HRESULT OnTapiEvent( IN TAPI_EVENT TapiEvent, IN IDispatch * pEvent ) { HRESULT hr = E_FAIL; switch ( TapiEvent ) { case TE_CALLNOTIFICATION: { // TE_CALLNOTIFICATION means that the application is being notified // of a new call. // hr = DoCallNotification(pEvent); break; }//TE_CALLNOTIFICATION case TE_CALLSTATE: { // // TE_CALLSTATE is a call state event. pEvent is // an ITCallStateEvent // hr = DoCallState(pEvent); break; }//TE_CALLSTATE case TE_CALLMEDIA: { // // TE_CALLMEDIA is a media event. pEvent is an ITCallMediaEvent // hr = DoCallMedia(pEvent); break; }//TE_CALLMEDIA case TE_FILETERMINAL: { // // TE_FILETERMINAL is a file terminal event. pEvent is an ITFileTerminalEvent // hr = DoFileEvent(pEvent); break; }//TE_FILETERMINAL default: break; } pEvent->Release(); // we addrefed CTAPIEventNotification::Event() return S_OK; } /////////////////////////////////////////////////////////////////// // CheckStreamMT // // Helper method - check if pITStream has the requested media type // /////////////////////////////////////////////////////////////////// HRESULT CheckStreamMT( IN ITStream* pITStream, IN long mt) { long mtStream = TAPIMEDIATYPE_AUDIO; HRESULT hr = E_FAIL; if(FAILED(hr=pITStream->get_MediaType(&mtStream))) { return hr; } if(!(mt&mtStream)) { return S_FALSE; } else { return S_OK; } } /////////////////////////////////////////////////////////////////// // CheckStreamDir // // Helper method - check if pITStream has the requested direction // /////////////////////////////////////////////////////////////////// HRESULT CheckStreamDir( IN ITStream* pITStream, IN TERMINAL_DIRECTION td) { TERMINAL_DIRECTION tdStream = TD_CAPTURE ; HRESULT hr =E_FAIL; if(FAILED(hr=pITStream->get_Direction(&tdStream))) { return hr; } if(td!=tdStream) { return S_FALSE; } else { return S_OK; } } /////////////////////////////////////////////////////////////////// // CreateAndSelectFileRecordTerminal // // Creates FileRecording terminal, add the audio track, put_FileName // select track on the stream // /////////////////////////////////////////////////////////////////// HRESULT CreateAndSelectFileRecordTerminal() { // // we should already have the call // if (NULL == g_pCall) { DoMessage( _T("CreateAndSelectFileRecordTerminal: we do not have a call")); return E_UNEXPECTED; } // // get the ITStreamControl interface for this call // ITStreamControl* pStreamControl = NULL; HRESULT hr = g_pCall->QueryInterface( IID_ITStreamControl, (void**)&pStreamControl ); if (FAILED(hr)) { DoMessage( _T("CreateAndSelectFileRecordTerminal: QI for ITStreamControl failed")); return E_FAIL; } // // make sure the record terminal was released // if(NULL != g_pRecordFileTerm) { g_pRecordFileTerm->Release(); g_pRecordFileTerm = NULL; } // // QI for ITBasicCallControl2 - prepare to request the terminal // ITBasicCallControl2* pITBCC2 = NULL; hr = g_pCall->QueryInterface( IID_ITBasicCallControl2, (void**)&pITBCC2 ); if(FAILED(hr)) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: QI for ITBasicCallControl2 failed")); return hr; } // // get the BSTR CLSID used for ReqestTerminal // BSTR bstrCLSID = NULL; hr = StringFromCLSID(CLSID_FileRecordingTerminal, &bstrCLSID); if(FAILED(hr)) { pITBCC2->Release(); pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: StringFromCLSID failed")); return hr; } // // request the record terminal - use the right direction and media type // hr = pITBCC2->RequestTerminal(bstrCLSID, TAPIMEDIATYPE_AUDIO, TD_RENDER, &g_pRecordFileTerm); // //clean up // ::CoTaskMemFree(bstrCLSID); pITBCC2->Release(); if(FAILED(hr)) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: RequestTerminal failed")); return hr; } // // get to the ITMediaRecord interface so we can put_FileName // we can NOT select before put_FileName // ITMediaRecord* pITMediaRec = NULL; hr = g_pRecordFileTerm->QueryInterface( IID_ITMediaRecord, (void**)&pITMediaRec ); if(FAILED(hr)) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: QI ITMediaRecord failed")); return hr; } // // we will have a file for each message // WCHAR wchFileName[MAX_PATH+1] = {0}; _snwprintf_s(wchFileName, MAX_PATH, _T("%s%ld%s"), REC_FILENAME, g_dwMessages, REC_FILEEXT); wchFileName[MAX_PATH] = 0; BSTR bstrFileName = ::SysAllocString(wchFileName); if(NULL == bstrFileName) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: SysAllocString failed")); return E_OUTOFMEMORY; } // // put_FileName // hr = pITMediaRec->put_FileName(bstrFileName); // //clean up // ::SysFreeString(bstrFileName); pITMediaRec->Release(); if(FAILED(hr)) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: put_FileName failed")); return hr; } // // get to the ITMultiTrackTerminal interface so we can create tracks // ITMultiTrackTerminal* pMTRecTerminal = NULL; hr = g_pRecordFileTerm->QueryInterface( IID_ITMultiTrackTerminal, (void**)&pMTRecTerminal ); if(FAILED(hr)) { pStreamControl->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: QI IID_ITMultiTrackTerminal failed")); return hr; } // // enumerate the streams on the call // IEnumStream* pEnumStreams = NULL; hr = pStreamControl->EnumerateStreams(&pEnumStreams); pStreamControl->Release(); if (FAILED(hr)) { pMTRecTerminal->Release(); DoMessage( _T("CreateAndSelectFileRecordTerminal: EnumerateStreams failed")); return hr; } // // only do audio for now // long lMediaTypes = TAPIMEDIATYPE_AUDIO; // // walk through the list of streams on the call // create what we need to // ITStream* pStream = NULL; while (S_OK == pEnumStreams->Next(1, &pStream, NULL)) { if ( (S_OK==CheckStreamMT(pStream,lMediaTypes)) && (S_OK==CheckStreamDir(pStream,TD_RENDER))) { // // if we have a recording terminal, attempt to create a track terminal // ITTerminal* pRecordingTrack = NULL; // //check stream for media type - when we check we have an & // long lStreamMediaType=0; hr = pStream->get_MediaType(&lStreamMediaType); if (FAILED(hr)) { pStream->Release(); pStream=NULL; continue; } // // create track terminal // hr = pMTRecTerminal->CreateTrackTerminal(lStreamMediaType, TD_RENDER, &pRecordingTrack); if (FAILED(hr)) { pStream->Release(); pStream=NULL; // //will fail at the end anyway - lMediaTypes!=0 - so break here // break; } // //select it on stream // hr = pStream->SelectTerminal(pRecordingTrack); // //clean up // pStream->Release(); pStream=NULL; pRecordingTrack->Release(); pRecordingTrack=NULL; if (FAILED(hr)) { // //will fail at the end anyway - lMediaTypes!=0 - so break here // break; } lMediaTypes^=lStreamMediaType; if(lMediaTypes==0) { // //we selected terminals for all media types we needed, we're done // break; } } // // clean up // if(pStream!=NULL) { pStream->Release(); pStream = NULL; } } // while (enumerating streams on the call) // //clean up // pMTRecTerminal->Release(); pEnumStreams->Release(); // //check if we were able to select for all media types // if (lMediaTypes==0) { return S_OK; } else { DoMessage( _T("CreateAndSelectFileRecordTerminal: no streams available")); return E_FAIL; } } /////////////////////////////////////////////////////////////////// // SameCall // // check if same call object as g_pCall // /////////////////////////////////////////////////////////////////// bool SameCall(ITCallStateEvent* pCallStateEvent) { // // sanity check // if(NULL == pCallStateEvent) { return false; } // //get call object // ITCallInfo* pCallInfo = NULL; HRESULT hr = pCallStateEvent->get_Call(&pCallInfo); // //check if we have a call and get_Call succeded // if(NULL != pCallInfo && ( SUCCEEDED(hr) && NULL != g_pCall) ) { bool bIsEqual = true; // //get and compare IUnknown for both objects // IUnknown* pIUnkCallInfo = NULL; IUnknown* pIUnkCallControl = NULL; pCallInfo->QueryInterface(IID_IUnknown, (void**)&pIUnkCallInfo); g_pCall->QueryInterface(IID_IUnknown, (void**)&pIUnkCallControl); // //compare // if(pIUnkCallInfo != pIUnkCallControl) { bIsEqual = false; } // //clean up // pCallInfo->Release(); if(NULL != pIUnkCallInfo) { pIUnkCallInfo->Release(); } if(NULL != pIUnkCallControl) { pIUnkCallControl->Release(); } return bIsEqual; } else { return false; } } /////////////////////////////////////////////////////////////////// // DoCallNotification // // TE_CALLNOTIFICATION means that the application is being notified // of a new call. // // Note that we don't answer to call at this point. The application // should wait for a CS_OFFERING CallState message before answering // the call. // /////////////////////////////////////////////////////////////////// HRESULT DoCallNotification( IN IDispatch * pEvent) { ITCallNotificationEvent * pNotify = NULL; HRESULT hr = pEvent->QueryInterface( IID_ITCallNotificationEvent, (void **)&pNotify ); if (FAILED(hr)) { DoMessage( _T("Incoming call, but failed to get the interface")); return hr; } CALL_PRIVILEGE cp = CP_MONITOR; ITCallInfo * pCall = NULL; // // get the call // hr = pNotify->get_Call( &pCall ); // // release the event object // pNotify->Release(); if(FAILED(hr)) { DoMessage( _T("Incoming call, but failed to get the call")); return hr; } // // check to see if we own the call // hr = pCall->get_Privilege( &cp ); if(FAILED(hr)) { pCall->Release(); DoMessage( _T("Incoming call, but failed to get_Privilege")); return hr; } if ( CP_OWNER != cp ) { // // just ignore it if we don't own it // pCall->Release(); return hr; } // //check if we already have a call - if yes reject it // if(NULL != g_pCall) { ITBasicCallControl* pITBCC = NULL; hr = pCall->QueryInterface( IID_ITBasicCallControl, (void**)&pITBCC ); pCall->Release(); // //sanity check // if(SUCCEEDED(hr)) { // //disconnect - we'll handle the other events from this call later // pITBCC->Disconnect(DC_REJECTED); pITBCC->Release(); } return hr; } // //get the call if do not have already one // hr = pCall->QueryInterface( IID_ITBasicCallControl, (void**)&g_pCall ); pCall->Release(); if(FAILED(hr)) { DoMessage( _T("Incoming call, but failed to QI ITBasicCallControl")); } else { // // update UI // SetStatusMessage(_T("Incoming Owner Call")); } // //clean up // return hr; } /////////////////////////////////////////////////////////////////// // DoCallState // // TE_CALLSTATE is a call state event. pEvent is // an ITCallStateEvent // /////////////////////////////////////////////////////////////////// HRESULT DoCallState( IN IDispatch * pEvent) { CALL_STATE cs = CS_IDLE; ITCallStateEvent * pCallStateEvent = NULL; // // Get the interface // HRESULT hr = pEvent->QueryInterface( IID_ITCallStateEvent, (void **)&pCallStateEvent ); if(FAILED(hr)) { DoMessage( _T("CallStateEvent, but failed to get the interface")); return hr; } // //check if same call - if it is not do not process the event // if(false == SameCall(pCallStateEvent)) { pCallStateEvent->Release(); return hr; } // // get the CallState that we are being notified of. // hr = pCallStateEvent->get_State( &cs ); pCallStateEvent->Release(); if(FAILED(hr)) { DoMessage( _T("ITCallStateEvent, but failed to get_State")); return hr; } // // send messages to main dialog procedure // if (CS_OFFERING == cs) { PostMessage(g_hDlg, WM_COMMAND, IDC_ANSWER, 0); } else if (CS_DISCONNECTED == cs) { PostMessage(g_hDlg, WM_COMMAND, IDC_DISCONNECTED, 0); } else if (CS_CONNECTED == cs) { PostMessage(g_hDlg, WM_COMMAND, IDC_CONNECTED, 0); } return hr; } /////////////////////////////////////////////////////////////////// // DoCallMedia // // // TE_CALLMEDIA is a media event. pEvent is an ITCallMediaEvent // // /////////////////////////////////////////////////////////////////// HRESULT DoCallMedia( IN IDispatch * pEvent) { CALL_MEDIA_EVENT cme = CME_STREAM_INACTIVE; ITCallMediaEvent * pCallMediaEvent = NULL; // // check if we still have a call // if(NULL == g_pCall) { return E_UNEXPECTED; } // // Get the interface // HRESULT hr = pEvent->QueryInterface( IID_ITCallMediaEvent, (void **)&pCallMediaEvent ); if(FAILED(hr)) { DoMessage( _T("ITCallMediaEvent, but failed to get the interface")); return hr; } // // get the CALL_MEDIA_EVENT that we are being notified of. // hr = pCallMediaEvent->get_Event( &cme ); if(FAILED(hr)) { pCallMediaEvent->Release(); DoMessage( _T("ITCallMediaEvent, but failed to get_Event")); return hr; } switch ( cme ) { // // the only event we process // case CME_STREAM_ACTIVE: { // // Get the terminal that's now active. // ITTerminal * pTerminal = NULL; hr = GetTerminalFromStreamEvent(pCallMediaEvent, &pTerminal); if ( FAILED(hr) ) { DoMessage(_T("ITCallMediaEvent: GetTerminalFromStreamEvent failed")); g_pCall->Disconnect(DC_NORMAL); break; } // // Process this terminal based on the direction. // TERMINAL_DIRECTION td; hr = pTerminal->get_Direction( &td); // //clean up // pTerminal->Release(); if ( FAILED(hr) ) { DoMessage(_T("ITCallMediaEvent: get_Direction failed")); g_pCall->Disconnect(DC_NORMAL); break; } // // if TD_CAPTURE and we have playback terminal start streaming // if ( TD_CAPTURE == td && NULL != g_pPlayFileTerm) { ITMediaControl* pITMC = NULL; hr = g_pPlayFileTerm->QueryInterface(IID_ITMediaControl, (void**)&pITMC); // // get ITMediaControl so we can start streaming // if(FAILED(hr)) { DoMessage(_T("ITCallMediaEvent: g_pPlayFileTerm QI for ITMediaControl failed")); g_pCall->Disconnect(DC_NORMAL); break; } // // Start streaming // hr = pITMC->Start(); pITMC->Release(); if(SUCCEEDED(hr)) { SetStatusMessage(_T("File Playback Terminal started ")); } else { DoMessage(_T("ITCallMediaEvent: ITMediaControl::Start() failed")); g_pCall->Disconnect(DC_NORMAL); } } break; } default: break; } // // clean up // pCallMediaEvent->Release(); return hr; } /////////////////////////////////////////////////////////////////// // DoFileEvent // // // TE_FILETERMINAL is a file terminal event. pEvent is an ITFileTerminalEvent // // /////////////////////////////////////////////////////////////////// HRESULT DoFileEvent( IN IDispatch * pEvent) { // // check if we still have a call // if(NULL == g_pCall) { return E_UNEXPECTED; } ITFileTerminalEvent* pITFTEvent = NULL; HRESULT hr = pEvent->QueryInterface( IID_ITFileTerminalEvent, reinterpret_cast(&pITFTEvent) ); if (FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to get the interface")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // get the state - we'll make some decision based on this // TERMINAL_MEDIA_STATE ftState=TMS_ACTIVE; hr = pITFTEvent->get_State(&ftState); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // pITFTEvent->Release(); DoMessage( _T("ITFileTerminalEvent, but failed to get_State")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // we are interesred in TMS_IDLE because we will unselect playback and // select recording // if(ftState != TMS_IDLE) { pITFTEvent->Release(); return hr; } // // get the terminal // ITTerminal *pTerminal = NULL; hr = pITFTEvent->get_Terminal(&pTerminal); // // do not need this anymore // pITFTEvent->Release(); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to get_Terminal")); g_pCall->Disconnect(DC_NORMAL); return hr; } TERMINAL_DIRECTION td; hr = pTerminal->get_Direction( &td); pTerminal->Release(); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to get_Direction")); g_pCall->Disconnect(DC_NORMAL); return hr; } if((td == TD_CAPTURE) && (NULL != g_pPlayFileTerm)) { // // unselect playback - it is done - we reached the end or an error // ITBasicCallControl2 *pITBCC2 = NULL; hr = g_pCall->QueryInterface(IID_ITBasicCallControl2, (void**)&pITBCC2); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to QI for ITBasicCallControl2")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // use ITBasicCallControl2 methods - much easier than // enumerate stream, terminals, etc // hr = pITBCC2->UnselectTerminalOnCall(g_pPlayFileTerm); g_pPlayFileTerm->Release(); g_pPlayFileTerm = NULL; pITBCC2->Release(); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to ITBasicCallControl2::UnselectTerminalOnCall")); g_pCall->Disconnect(DC_NORMAL); return hr; } // // select record - do not use automatic selection // we'll see how to use ITMultiTrackTerminal methods // hr = CreateAndSelectFileRecordTerminal(); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // g_pCall->Disconnect(DC_NORMAL); SetStatusMessage(_T("CreateAndSelectFileRecordTerminal failed")); return hr; } // // get ITMediaControl interface - we need to call Start // ITMediaControl* pITMC = NULL; hr = g_pRecordFileTerm->QueryInterface(IID_ITMediaControl, (void**)&pITMC); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but failed to QI ITMediaControl")); g_pCall->Disconnect(DC_NORMAL); return hr; } hr = pITMC->Start(); pITMC->Release(); if(FAILED(hr)) { // // fatal error - can not continue - disconnect the call // DoMessage( _T("ITFileTerminalEvent, but ITMediaControl::Start")); g_pCall->Disconnect(DC_NORMAL); return hr; } SetStatusMessage(_T("File Record Terminal started ")); // // will stop recording after one minute // SetTimer(g_hDlg, TIMER_ID, MAX_REC_TIME, NULL); g_dwMessages++; } return hr; }