Steps to integrate to Plantronics:
- Install the SDK
- Cut and Paste code from below!
[New!] Amazon Connect Sample Code – to enable Plantronics Headset Call Control:
See Articles section: Plantronics Headset Call Control with Amazon Connect.
Questions? Browse the Articles and use the forum.
// Program.cs : C# sample demonstrating basic call control. // NOTE: add a reference to: C:\Program Files (x86)\Plantronics\Spokes3G SDK\Interop.Plantronics.dll using System; using System.Runtime.InteropServices; using Interop.Plantronics; namespace PLTCSharpSample { class Program { // Plantronics API objects and interfaces required private static COMSessionManager _sessionManager; private static COMSession _session; private static ICOMSessionManagerEvents_Event _sessionManagerEvents; private static COMDevice _device; private static ICOMDeviceListenerEvents_Event _deviceListenerEvents; private static COMCallCommand _callCommand; private static COMDeviceListener _deviceListener; private static int _callid; // variable to track call id between my app and Plantronics static void Main() { Console.WriteLine("C# Plantronics COM API Sample"); bool quit = false; // Connect to the Plantronics COM API _sessionManager = new COMSessionManager(); _sessionManager.Register("My App", out _session); _sessionManagerEvents = (ICOMSessionManagerEvents_Event)_sessionManager; // obtain session manager events interface _sessionManagerEvents.onDeviceStateChanged += _sessionManagerEvents_onDeviceStateChanged; // register for device state changed events (device arrival / removal) _session.onCallStateChanged += _session_onCallStateChanged; // register for call state changed events (headset call control) _callCommand = _session.GetCallCommand(); // obtain Plantronics object for controlling call state of Plantronics device AttachDevice(); // register for device events and obtain device listener while (!quit) { ShowMenu(); string cmd = Console.ReadLine(); switch (cmd) { case "1": _callid++; DoIncomingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call break; case "2": _callid++; DoOutgoingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call break; case "3": DoAnswerCall(_callid); // inform Plantronics my app has now answered an incoming (ringing) call break; case "4": DoHoldCall(_callid); // place call on hold break; case "5": DoResumeCall(_callid); // resume the call break; case "6": DoMuteCall(true); // mute the headset (note for wireless products, audio link must be active) break; case "7": DoMuteCall(false); // unmute the headset (note for wireless products, audio link must be active) break; case "8": DoTerminateCall(_callid); // inform Plantronics my app has now terminated the call break; case "0": quit = true; break; default: Console.WriteLine("Unrecognised menu choice."); break; } } // Cleanup the Plantronics COM API DetachDevice(); _session.onCallStateChanged -= _session_onCallStateChanged; _sessionManagerEvents.onDeviceStateChanged -= _sessionManagerEvents_onDeviceStateChanged; _sessionManager.Unregister(_session); Marshal.ReleaseComObject(_session); Marshal.ReleaseComObject(_sessionManager); } private static void ShowMenu() { Console.WriteLine(); Console.WriteLine("plt sample menu"); Console.WriteLine("--"); Console.WriteLine("1 - ring/incoming call"); Console.WriteLine("2 - outgoing call"); Console.WriteLine("3 - answer call"); Console.WriteLine("4 - hold call"); Console.WriteLine("5 - resume call"); Console.WriteLine("6 - mute call"); Console.WriteLine("7 - unmute call"); Console.WriteLine("8 - end call"); Console.WriteLine("0 - quit"); Console.WriteLine(); Console.Write("> "); } private static void DoIncomingCall(int callid, string contactname) { COMCall call = new COMCall() { Id = callid }; Contact contact = new Contact() { Name = contactname }; _callCommand.IncomingCall(call, contact, CallRingTone.RingTone_Unknown, CallAudioRoute.AudioRoute_ToHeadset); Console.WriteLine("Performing incoming call, id = " + callid); } private static void DoOutgoingCall(int callid, string contactname) { Console.WriteLine("Performing outgoing call, id = " + callid); COMCall call = new COMCall() { Id = callid }; Contact contact = new Contact() { Name = contactname }; _callCommand.OutgoingCall(call, contact, CallAudioRoute.AudioRoute_ToHeadset); } private static void DoAnswerCall(int callid) { Console.WriteLine("Performing outgoing call, id = " + callid); COMCall call = new COMCall() { Id = callid }; _callCommand.AnsweredCall(call); } private static void DoHoldCall(int callid) { Console.WriteLine("Performing outgoing call, id = " + callid); COMCall call = new COMCall() { Id = callid }; _callCommand.HoldCall(call); } private static void DoResumeCall(int callid) { Console.WriteLine("Performing outgoing call, id = " + callid); COMCall call = new COMCall() { Id = callid }; _callCommand.ResumeCall(call); } private static void DoMuteCall(bool mute) { Console.WriteLine("Setting headset mute = " + mute); _deviceListener.mute = mute; } private static void DoTerminateCall(int callid) { Console.WriteLine("Performing outgoing call, id = " + callid); COMCall call = new COMCall() { Id = callid }; _callCommand.TerminateCall(call); } private static void _session_onCallStateChanged(COMCallEventArgs callEventArgs) { // informs us the calling state has changed, for example user as answered/terminated a call // using headset buttons - this event should be used in my app to actually connect/terminate the call! Console.WriteLine("Call State Changed: callid=" + callEventArgs.call.Id + " new state=" + callEventArgs.CallState); } private static void _sessionManagerEvents_onDeviceStateChanged(COMStateDeviceEventArgs deviceEventArgs) { // device may have arrived or been removed. Either way detach device event handlers, then try to re-attach them DetachDevice(); AttachDevice(); } private static void AttachDevice() { // Lets get the primary Plantronics device (if any) and then register // for the device event handlers try { _device = _session.GetActiveDevice(); if (_device != null) { // display device information: Console.WriteLine("Device attached: " + _device.ProductName + ", Product ID = " + _device.ProductId.ToString("X")); _deviceListenerEvents = (ICOMDeviceListenerEvents_Event)_device.DeviceListener; if (_deviceListenerEvents != null) { _deviceListenerEvents.onHeadsetStateChanged += _deviceListenerEvents_onHeadsetStateChanged; Console.WriteLine("Successfully hooked to device listener events"); } else Console.WriteLine("Unable to hook to device listener events"); _deviceListener = _device.DeviceListener; // Obtain a DeviceListener object for later use Console.WriteLine("Device mute state: muted = " + _deviceListener.mute); // Obtain initial device microphone mute state } else Console.WriteLine("Unable to retrieve active device"); } catch (Exception) { Console.WriteLine("Unable to retrieve/hook to active device"); } } private static void _deviceListenerEvents_onHeadsetStateChanged(COMDeviceListenerEventArgs args) { // informs us of a variety of Plantronics device state changes Console.Write(args.DeviceEventType + " - "); if (args.DeviceEventType == COMDeviceEventType.DeviceEventType_HeadsetStateChanged) { Console.Write(args.HeadsetStateChange); } Console.WriteLine(); } private static void DetachDevice() { // Lets un-register the Plantronics device event handlers if (_device != null) { Marshal.ReleaseComObject(_device); _device = null; } if (_deviceListenerEvents != null) { _deviceListenerEvents.onHeadsetStateChanged -= _deviceListenerEvents_onHeadsetStateChanged; Marshal.ReleaseComObject(_deviceListenerEvents); _deviceListenerEvents = null; } } // Convenience class for passing a COMContact to CallCommand IncomingCall/OutgoingCall methods class Contact : COMContact { public string Email { get; set; } public string FriendlyName { get; set; } public string HomePhone { get; set; } public int Id { get; set; } public string MobilePhone { get; set; } public string Name { get; set; } public string Phone { get; set; } public string SipUri { get; set; } public string WorkPhone { get; set; } } } }
// PLTCPlusPlusSample.cpp : C++ COM sample demonstrating basic call control. // NOTE: Spokes 3.0 COM SDK is distributed as tlb file. // C++ user can use #import directive (see below) that will create all proper C++ types, wrappers and interfaces for // communicating with running PLTHub.exe COM server #include <stdio.h> #include <tchar.h> #include <atlbase.h> #include <atlstr.h> #include <fstream> #include <istream> #include <iostream> #include <sstream> #include <string> #include <iomanip> #include <iostream> #include <sstream> #include <string> #include "atlcom.h" #include <SDKDDKVer.h> #import "Plantronics.tlb" no_namespace, named_guids, raw_interfaces_only #ifdef _MSC_VER #if _MSC_VER < 1900 // Version of the compiler before 19 (VS2015) did not properly handle this #define __func__ __FUNCTION__ #define noexcept #endif #endif #define _ATL_DEBUG_QI #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // some CString constructors will be explicit using namespace std; using namespace ATL; // We need this as project is created as Win console app, and we are instantiating ATL COM objects that are used as Event sink's CComModule _Module; extern __declspec(selectany) CAtlModule* _pAtlModule=&_Module; #define PRINT_ERROR(str, res) std::cout << str << "Error Code: " << std::hex << res << std::endl ICOMSessionManagerPtr sessionManager; ICOMSessionPtr session; // Helper functions to print meaningful string representations of enums std::string EnumToString(CallState val) { std::string ret; switch (val) { case CallState_Unknown: ret = "CallState_Unknown"; break; case CallState_AcceptCall: ret = "CallState_AcceptCall"; break; case CallState_TerminateCall: ret = "CallState_TerminateCall"; break; case CallState_HoldCall: ret = "CallState_HoldCall"; break; case CallState_Resumecall: ret = "CallState_Resumecall"; break; case CallState_Flash: ret = "CallState_Flash"; break; case CallState_CallInProgress: ret = "CallState_CallInProgress"; break; case CallState_CallRinging: ret = "CallState_CallRinging"; break; case CallState_CallEnded: ret = "CallState_CallEnded"; break; case CallState_TransferToHeadSet: ret = "CallState_TransferToHeadSet"; break; case CallState_TransferToSpeaker: ret = "CallState_TransferToSpeaker"; break; case CallState_MuteON: ret = "CallState_MuteON"; break; case CallState_MuteOFF: ret = "CallState_MuteOFF"; break; case CallState_MobileCallRinging: ret = "CallState_MobileCallRinging"; break; case CallState_MobileCallInProgress: ret = "CallState_MobileCallInProgress"; break; case CallState_MobileCallEnded: ret = "CallState_MobileCallEnded"; break; case CallState_Don: ret = "CallState_Don"; break; case CallState_Doff: ret = "CallState_Doff"; break; case CallState_CallIdle: ret = "CallState_CallIdle"; break; case CallState_Play: ret = "CallState_Play"; break; case CallState_Pause: ret = "CallState_Pause"; break; case CallState_Stop: ret = "CallState_Stop"; break; case CallState_DTMFKey: ret = "CallState_DTMFKey"; break; case CallState_RejectCall: ret = "CallState_RejectCall"; break; } return ret; } std::string EnumToString(DeviceHeadsetStateChange val) { std::string ret; switch (val) { case HeadsetStateChange_Unknown: ret = "HeadsetStateChange_Unknown"; break; case HeadsetStateChange_MonoON: ret = "HeadsetStateChange_MonoON"; break; case HeadsetStateChange_MonoOFF: ret = "HeadsetStateChange_MonoOFF"; break; case HeadsetStateChange_StereoON: ret = "HeadsetStateChange_StereoON"; break; case HeadsetStateChange_StereoOFF: ret = "HeadsetStateChange_StereoOFF"; break; case HeadsetStateChange_MuteON: ret = "HeadsetStateChange_MuteON"; break; case HeadsetStateChange_MuteOFF: ret = "HeadsetStateChange_MuteOFF"; break; case HeadsetStateChange_BatteryLevel: ret = "HeadsetStateChange_BatteryLevel"; break; case HeadsetStateChange_InRange: ret = "HeadsetStateChange_InRange"; break; case HeadsetStateChange_OutofRange: ret = "HeadsetStateChange_OutofRange"; break; case HeadsetStateChange_Docked: ret = "HeadsetStateChange_Docked"; break; case HeadsetStateChange_UnDocked: ret = "HeadsetStateChange_UnDocked"; break; case HeadsetStateChange_InConference: ret = "HeadsetStateChange_InConference"; break; case HeadsetStateChange_Don: ret = "HeadsetStateChange_Don"; break; case HeadsetStateChange_Doff: ret = "HeadsetStateChange_Doff"; break; case HeadsetStateChange_SerialNumber: ret = "HeadsetStateChange_SerialNumber"; break; case HeadsetStateChange_Near: ret = "HeadsetStateChange_Near"; break; case HeadsetStateChange_Far: ret = "HeadsetStateChange_Far"; break; case HeadsetStateChange_DockedCharging: ret = "HeadsetStateChange_DockedCharging"; break; case HeadsetStateChange_ProximityUnknown: ret = "HeadsetStateChange_ProximityUnknown"; break; case HeadsetStateChange_ProximityEnabled: ret = "HeadsetStateChange_ProximityEnabled"; break; case HeadsetStateChange_ProximityDisabled: ret = "HeadsetStateChange_ProximityDisabled"; break; } return ret; } std::string EnumToString(DeviceChangeState val) { std::string ret; switch (val) { case DeviceState_Unknown: ret = "DeviceState_Unknown"; break; case DeviceState_Added: ret = "DeviceState_Added"; break; case DeviceState_Removed: ret = "DeviceState_Removed"; break; } return ret; } std::string EnumToString(COMDeviceEventType val) { std::string ret; switch (val) { case DeviceEventType_Unknown: ret = "DeviceEventType_Unknown"; break; case DeviceEventType_HeadsetButtonPressed: ret = "DeviceEventType_HeadsetButtonPressed"; break; case DeviceEventType_HeadsetStateChanged: ret = "DeviceEventType_HeadsetStateChanged"; break; case DeviceEventType_BaseButtonPressed: ret = "DeviceEventType_BaseButtonPressed"; break; case DeviceEventType_BaseStateChanged: ret = "DeviceEventType_BaseStateChanged"; break; case DeviceEventType_ATDButtonPressed: ret = "DeviceEventType_ATDButtonPressed"; break; case DeviceEventType_ATDStateChanged: ret = "DeviceEventType_ATDStateChanged"; break; } return ret; } // CallObject class ATL_NO_VTABLE CallObject : public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<ICOMCall, &__uuidof(ICOMCall), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 > { public: CallObject() { } CallObject(long id) { put_Id(id); } static CComObject<CallObject>* GetCallObject() { CComObject<CallObject>* pCall; CComObject<CallObject>::CreateInstance(&pCall); pCall->AddRef(); // this object is created with ref count 0; std::string strCallId; std::cout << "Enter Call Id: "; std::getline(std::cin, strCallId); long id; std::stringstream myStream(strCallId); myStream >> id; pCall->put_Id(id); return pCall; } // Constructor that takes the call id as a parameter static CComObject<CallObject>* GetCallObject(long id) { CComObject<CallObject>* pCall; CComObject<CallObject>::CreateInstance(&pCall); pCall->AddRef(); // this object is created with ref count 0; pCall->put_Id(id); return pCall; } BEGIN_COM_MAP(CallObject) COM_INTERFACE_ENTRY2(IDispatch, ICOMCall) COM_INTERFACE_ENTRY(ICOMCall) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: private: long m_id; // ICOMCall Methods public: STDMETHOD(get_Id)(long * pVal) { *pVal = m_id; return S_OK; } STDMETHOD(put_Id)(long pVal) { m_id = pVal; return S_OK; } STDMETHOD(get_ConferenceId)(long * pVal) { return E_NOTIMPL; } STDMETHOD(put_ConferenceId)(long pVal) { return E_NOTIMPL; } STDMETHOD(get_InConference)(VARIANT_BOOL * pVal) { return E_NOTIMPL; } STDMETHOD(put_InConference)(VARIANT_BOOL pVal) { return E_NOTIMPL;} }; // CallContact class ATL_NO_VTABLE CallContact : public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<ICOMContact, &__uuidof(ICOMContact), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 > { private: _bstr_t m_name; _bstr_t m_friendName; LONG m_id; _bstr_t m_sipUri; _bstr_t m_phone; _bstr_t m_email; _bstr_t m_workPhone; _bstr_t m_mobilePhone; _bstr_t m_homePhone; public: CallContact() { static LONG id = 0; m_id = ++id; } ~CallContact() { } static CComObject<CallContact>* GetContactObject() { CComObject<CallContact>* pContact; CComObject<CallContact>::CreateInstance(&pContact); pContact->AddRef(); // this object is created with ref count 0; std::string name; std::cout << "Enter Contact Name: "; std::getline(std::cin, name); pContact->put_Name(_bstr_t(name.c_str())); return pContact; } // Added a function override taking contact name as a parameter static CComObject<CallContact>* GetContactObject(std::string name) { CComObject<CallContact>* pContact; CComObject<CallContact>::CreateInstance(&pContact); pContact->AddRef(); // this object is created with ref count 0; pContact->put_Name(_bstr_t(name.c_str())); return pContact; } BEGIN_COM_MAP(CallContact) COM_INTERFACE_ENTRY2(IDispatch, ICOMContact) COM_INTERFACE_ENTRY(ICOMContact) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: // ICOMContact Methods public: STDMETHOD(get_Name)(BSTR * pVal) { *pVal = SysAllocString(m_name); return S_OK; } STDMETHOD(put_Name)(BSTR pVal) { m_name = pVal; return S_OK; } STDMETHOD(get_FriendlyName)(BSTR * pVal) { *pVal = SysAllocString(m_friendName); return S_OK; } STDMETHOD(put_FriendlyName)(BSTR pVal) { m_friendName = pVal; return S_OK; } STDMETHOD(get_Id)(LONG * pVal) { *pVal = m_id; return S_OK; } STDMETHOD(put_Id)(LONG pVal) { m_id = pVal; return S_OK; } STDMETHOD(get_SipUri)(BSTR * pVal) { *pVal = SysAllocString(m_sipUri); return S_OK; } STDMETHOD(put_SipUri)(BSTR pVal) { m_sipUri = pVal; return S_OK; } STDMETHOD(get_Phone)(BSTR * pVal) { *pVal = SysAllocString(m_phone); return S_OK; } STDMETHOD(put_Phone)(BSTR pVal) { m_phone = pVal; return S_OK; } STDMETHOD(get_Email)(BSTR * pVal) { *pVal = SysAllocString(m_email); return S_OK; } STDMETHOD(put_Email)(BSTR pVal) { m_email = pVal; return S_OK; } STDMETHOD(get_WorkPhone)(BSTR * pVal) { *pVal = SysAllocString(m_workPhone); return S_OK; } STDMETHOD(put_WorkPhone)(BSTR pVal) { m_workPhone = pVal; return S_OK; } STDMETHOD(get_MobilePhone)(BSTR * pVal) { *pVal = SysAllocString(m_mobilePhone); return S_OK; } STDMETHOD(put_MobilePhone)(BSTR pVal) { m_mobilePhone = pVal; return S_OK; } STDMETHOD(get_HomePhone)(BSTR * pVal) { *pVal = SysAllocString(m_homePhone); return S_OK; } STDMETHOD(put_HomePhone)(BSTR pVal) { m_homePhone = pVal; return S_OK; } }; _ATL_FUNC_INFO OnDeviceEvents = { CC_STDCALL, VT_EMPTY, 1,{ VT_DISPATCH } }; // DeviceListenerEventSink() : COM class : Sink for DeviceListener Events class DeviceListenerEventSink : public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<ICOMDeviceListenerEvents, &__uuidof(ICOMDeviceListenerEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0> { public: BEGIN_COM_MAP(DeviceListenerEventSink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY_IID(__uuidof(ICOMDeviceListenerEvents), IDispatch) END_COM_MAP() // ICOMDeviceListenerEvents Methods STDMETHOD(onHeadsetStateChanged)(struct ICOMDeviceListenerEventArgs *args); STDMETHOD(onHeadsetButtonPressed)(struct ICOMDeviceListenerEventArgs *args); STDMETHOD(onBaseButtonPressed)(struct ICOMDeviceListenerEventArgs *args); STDMETHOD(onBaseStateChanged)(struct ICOMDeviceListenerEventArgs *args); STDMETHOD(onATDStateChanged)(struct ICOMDeviceListenerEventArgs *args); DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } }; bool gGotDevice = false; // DeviceListenerSink() : user defined class : wrapper to Advise/Unadvise on Device Listener events class DeviceListenerSink { public: DeviceListenerSink(ICOMSessionPtr session) { if (session != nullptr) { ICOMDevicePtr device; if (SUCCEEDED(session->GetActiveDevice(&device)) && device != nullptr) { if (SUCCEEDED(device->get_DeviceListener(&m_deviceListener)) && m_deviceListener != nullptr) { if (SUCCEEDED(CComObject<DeviceListenerEventSink>::CreateInstance(&m_sink)) && m_sink != nullptr) { m_sink->AddRef(); HRESULT hr = AtlAdvise(m_deviceListener, m_sink, __uuidof(ICOMDeviceListenerEvents), &m_cookie); if (FAILED(hr)) PRINT_ERROR("Advise Device Listener event Error: ", hr); else { gGotDevice = true; } } } } } } ~DeviceListenerSink() { if (m_deviceListener != nullptr) { HRESULT hr = AtlUnadvise(m_deviceListener, __uuidof(ICOMDeviceListenerEvents), m_cookie); if (FAILED(hr)) PRINT_ERROR("Unadvise Device Listener event Error: ", hr); } if (m_sink != nullptr) { m_sink->Release(); } gGotDevice = false; } private: CComObject<DeviceListenerEventSink> *m_sink = nullptr; ICOMDeviceListenerPtr m_deviceListener = nullptr; DWORD m_cookie; }; // SessionEventSink() : COM class : Sink for Session Call Events class SessionEventSink : public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<ICOMCallEvents, &__uuidof(ICOMCallEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 > { public: BEGIN_COM_MAP(SessionEventSink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY_IID(__uuidof(ICOMCallEvents), IDispatch) END_COM_MAP() // ICOMCallEvents Methods STDMETHOD(onCallRequested)(struct ICOMCallRequestEventArgs *args); STDMETHOD(onCallStateChanged)(struct ICOMCallEventArgs *args); DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } }; // SessionManagerEventSink() : COM class : Sink for Session Manager events class SessionManagerEventSink : public CComObjectRootEx<CComSingleThreadModel>, public IDispatchImpl<ICOMSessionManagerEvents, &__uuidof(ICOMSessionManagerEvents), &LIBID_Spokes3GCOMServerLib, /*wMajor =*/ 3, /*wMinor =*/ 0 > { public: BEGIN_COM_MAP(SessionManagerEventSink) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY_IID(__uuidof(ICOMSessionManagerEvents), IDispatch) END_COM_MAP() // ICOMSessionManagerEvents Methods STDMETHOD(onDeviceStateChanged)(struct ICOMStateDeviceEventArgs *args); STDMETHOD(onCallStateChanged)(struct ICOMCallEventArgs *args); DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } }; // SessionSink() : user defined class : wrapper to Advise/Unadvise on Session Call events class SessionSink { public: SessionSink(ICOMSessionPtr session) { if (session != nullptr) { m_session = session; if (SUCCEEDED(CComObject<SessionEventSink>::CreateInstance(&m_sink)) && m_sink != nullptr) { m_sink->AddRef(); HRESULT hr = AtlAdvise(m_session, m_sink, __uuidof(ICOMCallEvents), &m_cookie); if (FAILED(hr)) PRINT_ERROR("Advise Session Call event Error", hr); } } } ~SessionSink() { if (m_session != nullptr) { HRESULT hr = AtlUnadvise(m_session, __uuidof(ICOMCallEvents), m_cookie); if (FAILED(hr)) PRINT_ERROR("Unadvise Session Call event Error", hr); } if (m_sink != nullptr) { m_sink->Release(); } } private: CComObject<SessionEventSink> *m_sink = nullptr; ICOMSessionPtr m_session = nullptr; DWORD m_cookie; }; // SessionManagerSink() : user defined class : wrapper to Advise/Unadvise on Session Manager events class SessionManagerSink { public: SessionManagerSink(ICOMSessionManagerPtr sessionManager) { m_sessionManager = sessionManager; CComObject<SessionManagerEventSink>::CreateInstance(&m_sink); if (m_sessionManager != nullptr && m_sink != nullptr) { m_sink->AddRef(); HRESULT hr = AtlAdvise(m_sessionManager, m_sink, __uuidof(ICOMSessionManagerEvents), &m_cookie); if (FAILED(hr)) PRINT_ERROR("Advise Session Manager events Error", hr); } } ~SessionManagerSink() { if (m_sessionManager != nullptr) { HRESULT hr = AtlUnadvise(m_sessionManager, __uuidof(ICOMSessionManagerEvents), m_cookie); if (FAILED(hr)) PRINT_ERROR("Unadvise Session Manager events Error", hr); } if (m_sink != nullptr) { m_sink->Release(); } } private: CComObject<SessionManagerEventSink> *m_sink = nullptr; ICOMSessionManagerPtr m_sessionManager = nullptr; DWORD m_cookie; }; // DeviceSink() : user defined class : wrapper to Advise/Unadvise on Device events class DeviceSink { public: DeviceSink(ICOMSessionPtr session) { if (session != nullptr) { if (SUCCEEDED(session->GetActiveDevice(&m_device)) && m_device != nullptr) { // hook to device listener event m_devListSink = new DeviceListenerSink(session); } } } ~DeviceSink() { delete m_devListSink; } private: DeviceListenerSink* m_devListSink = nullptr; ICOMDevicePtr m_device = nullptr; }; STDMETHODIMP SessionEventSink::onCallRequested(struct ICOMCallRequestEventArgs *requestArgs) { std::string contactName; ICOMContact* callContact = nullptr; requestArgs->get_contact(&callContact); if (callContact != nullptr) { BSTR name; callContact->get_Name(&name); contactName = (name != nullptr ? _bstr_t(name) : "none"); } std::cout << std::endl << "(Session) SessionEventSink onCallRequested contact: " << contactName; return S_OK; } STDMETHODIMP SessionEventSink::onCallStateChanged(struct ICOMCallEventArgs *callEventArgs) { CallState callState; callEventArgs->get_CallState(&callState); long callId(0); ICOMCall* call = nullptr; callEventArgs->get_call(&call); call->get_Id(&callId); std::cout << "Call State Changed: callid=" << callId << " new state=" << EnumToString(callState) << std::endl; return S_OK; } STDMETHODIMP SessionManagerEventSink::onDeviceStateChanged(struct ICOMStateDeviceEventArgs *args) { DeviceChangeState devState; args->get_DeviceState(&devState); std::cout << "onDeviceStateChanged: " << EnumToString(devState) << std::endl; gGotDevice = false; // schedule a device re-attach attempt next time around the main menu loop if (devState == DeviceState_Added) { std::cout << "Press Enter to attach this device." << std::endl; } return S_OK; } STDMETHODIMP SessionManagerEventSink::onCallStateChanged(struct ICOMCallEventArgs *callEventArgs) { return S_OK; } STDMETHODIMP DeviceListenerEventSink::onHeadsetStateChanged(struct ICOMDeviceListenerEventArgs *args) { DeviceHeadsetStateChange state; COMDeviceEventType evType; args->get_HeadsetStateChange(&state); args->get_DeviceEventType(&evType); if (evType == COMDeviceEventType::DeviceEventType_HeadsetStateChanged) { std::cout << EnumToString(evType) << ", state = " << EnumToString(state) << std::endl; } return S_OK; } STDMETHODIMP DeviceListenerEventSink::onHeadsetButtonPressed(struct ICOMDeviceListenerEventArgs *args) { DeviceHeadsetButton btn; COMDeviceEventType evType; args->get_HeadsetButton(&btn); args->get_DeviceEventType(&evType); if ((evType == COMDeviceEventType::DeviceEventType_HeadsetButtonPressed) && (btn == DeviceHeadsetButton::HeadsetButton_Mute)) { ICOMDevicePtr device; ICOMDeviceListenerPtr deviceListener; VARIANT_BOOL valBool; session->GetActiveDevice(&device); device->get_DeviceListener(&deviceListener); deviceListener->get_mute(&valBool); std::cout << "DeviceEventType_HeadsetStateChanged - "; (valBool == VARIANT_TRUE) ? std::cout << EnumToString(DeviceHeadsetStateChange::HeadsetStateChange_MuteON) : std::cout << EnumToString(DeviceHeadsetStateChange::HeadsetStateChange_MuteOFF); std::cout << std::endl; } return S_OK; } STDMETHODIMP DeviceListenerEventSink::onBaseButtonPressed(struct ICOMDeviceListenerEventArgs *args) { return S_OK; } STDMETHODIMP DeviceListenerEventSink::onBaseStateChanged(struct ICOMDeviceListenerEventArgs *args) { return S_OK; } STDMETHODIMP DeviceListenerEventSink::onATDStateChanged(struct ICOMDeviceListenerEventArgs *args) { return S_OK; } // Implementation of C++ COM sample DeviceSink* gDeviceSink = nullptr; // helper function that: // - gets the handle to the currently active device, // - gets a handle to deviceListner object // - prints its ProductName, ProductID & mute status of the device void PrintDeviceDetails() { HRESULT res = S_OK; BSTR productName; USHORT productId; VARIANT_BOOL valBool; ICOMDevicePtr myDevice; ICOMDeviceListenerPtr myDeviceListener; // get current active device from active session if (FAILED(res = session->GetActiveDevice(&myDevice))) { std::cout << "Unable to retrieve/hook to active device" << std::endl; return; } // get device listener interface if (FAILED(res = myDevice->get_DeviceListener(&myDeviceListener))) { std::cout << "Device is not attached"; return; } std::cout << "Successfully hooked to device listener events" << std::endl; // get & display device information myDevice->get_ProductName(&productName); myDevice->get_ProductId(&productId); _bstr_t bproductName(productName); std::cout << "Device attached: " << bproductName; std::wcout << L", Product ID = " << std::uppercase << std::hex << std::setfill(L'0') << std::setw(4) << productId << std::endl; // Obtain initial device microphone mute state res = myDeviceListener->get_mute(&valBool); std::cout << "Device mute state: muted = "; (valBool == VARIANT_TRUE) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl; return; } void ShowMenu() { std::cout << std::endl; std::cout << "plt sample menu" << std::endl; std::cout << "--" << std::endl; std::cout << "1 - ring/incoming call" << std::endl; std::cout << "2 - outgoing call" << std::endl; std::cout << "3 - answer call" << std::endl; std::cout << "4 - hold call" << std::endl; std::cout << "5 - resume call" << std::endl; std::cout << "6 - mute call" << std::endl; std::cout << "7 - unmute call" << std::endl; std::cout << "8 - end call" << std::endl; std::cout << "0 - quit" << std::endl; std::cout << std::endl; std::cout << "> "; } void DoIncomingCall(int callid, string contactname) { std::cout << "Performing IncomingCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->IncomingCall(CallObject::GetCallObject(callid), CallContact::GetContactObject(contactname), RingTone_Unknown, AudioRoute_ToHeadset); } void DoOutgoingCall(int callid, string contactname) { std::cout << "Performing OutgoingCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->OutgoingCall(CallObject::GetCallObject(callid), CallContact::GetContactObject(contactname), AudioRoute_ToHeadset); } void DoAnswerCall(int callid) { std::cout << "Performing AnswerCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->AnsweredCall(CallObject::GetCallObject(callid)); } void DoTerminateCall(int callid) { std::cout << "Performing TerminateCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->TerminateCall(CallObject::GetCallObject(callid)); } void DoHoldCall(int callid) { std::cout << "Performing HoldCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->HoldCall(CallObject::GetCallObject(callid)); } void DoResumeCall(int callid) { std::cout << "Performing ResumeCall, id = " << callid << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } res = callCommand->ResumeCall(CallObject::GetCallObject(callid)); } void DoMuteCall(int callid, bool mute) { std::cout << "Setting headset mute = "; (mute == true) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl; HRESULT res = S_OK; ICOMCallCommandPtr callCommand; if (FAILED(res = session->GetCallCommand(&callCommand))) { PRINT_ERROR("Error gettting interface from Call Command", res); return; } (mute == true) ? res = callCommand->MuteCall(CallObject::GetCallObject(callid), VARIANT_TRUE) : res = callCommand->MuteCall(CallObject::GetCallObject(callid), VARIANT_FALSE); } // sample entry point int _tmain(int argc, _TCHAR* argv[]) { bool quit = false; std::string input; int cmd = -1; int _callid = 0; // variable to track call id between my app and Plantronics // Initialize COM { ::CoInitializeEx(NULL, COINIT_MULTITHREADED); std::cout << "C++ Plantronics COM API Sample" << std::endl; // Create Spokes session manager object if (SUCCEEDED(sessionManager.CreateInstance(CLSID_COMSessionManager))) { // register Session Manager for device attached/detached events SessionManagerSink sessionManagerSink(sessionManager); // Register new session in Spokes. if (SUCCEEDED(sessionManager->Register(_bstr_t("COM Plugin"), &session))) { // register Session for call state change events SessionSink sessionSink(session); ICOMUserPreference *userPref; sessionManager->get_UserPreference(&userPref); userPref->put_DefaultSoftphone(_bstr_t("COM Plugin")); while (!quit) { // If we have not attached to device (or device was removed/added), then attempt to attach to a device if (!gGotDevice) { if (gDeviceSink != nullptr) { delete gDeviceSink; } std::cout << "Attempting device attach" << std::endl; // Instantiate Device Sink object and hook to DeviceListner events gDeviceSink = new DeviceSink(session); // call to AttachDevice() PrintDeviceDetails(); } ShowMenu(); std::getline(std::cin, input); // Convert from string to number safely. std::stringstream inputStringStream(input); if (!(inputStringStream >> cmd)) { cmd = -1; // check if device is attached and prompt error if it is if (gGotDevice == true) std::cout << "Invalid input, please try again" << std::endl; } switch (cmd) { case 1: _callid++; DoIncomingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call break; case 2: _callid++; DoOutgoingCall(_callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call break; case 3: DoAnswerCall(_callid); // inform Plantronics my app has now answered an incoming (ringing) call break; case 4: DoHoldCall(_callid); // place call on hold break; case 5: DoResumeCall(_callid); // resume the call break; case 6: DoMuteCall(_callid, true); // mute the headset (note for wireless products, audio link must be active) break; case 7: DoMuteCall(_callid, false); // unmute the headset (note for wireless products, audio link must be active) break; case 8: DoTerminateCall(_callid); // inform Plantronics my app has now terminated the call break; case 0: quit = true; break; default: if (gGotDevice == true) std::cout << "Unrecognised menu choice, please try again." << std::endl; break; } Sleep(250); } // cleanup unregister session if (gDeviceSink != nullptr) { delete gDeviceSink; } sessionManager->Unregister(session); } } } // uninitialize COM ::CoUninitialize(); return 0; }
// PlantronicsRESTDemo.java : Java sample demonstrating basic call control. package plantronicsrestdemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; // Pre-requisite, install Plantronics Hub from: http://www.plantronics.com/software public class PlantronicsRESTDemo { static Boolean quit = false; static String sessionid = ""; static Boolean pluginRegistered = false; static int callid = 0; private static EventsListenerThread eventslistener = null; private static String BaseURL = "http://127.0.0.1:32017/"; /** * @param args the command line arguments */ public static void main(String[] args) { System.out.println("Java Plantronics REST API Sample"); DoAttachDevice(); // Connect to the Plantronics REST API DoShowDeviceInfo(); // get info about attached device (if any) // start polling the API for device/call events or to reconnect to API (in the event of connection failure)... DoStartEventsListener(sessionid); while (!quit) { ShowMenu(); String cmd = ReadCommand(); switch (cmd) { case "1": DoIncomingCall(++callid, "Bob%20Smith"); // inform Plantronics my app has an incoming (ringing) call break; case "2": DoOutgoingCall(++callid, "Bob%20Smith"); // inform Plantronics my app has an outgoing call break; case "3": DoAnswerCall(callid); // inform Plantronics my app has now answered an incoming (ringing) call break; case "4": DoHoldCall(callid); // place call on hold break; case "5": DoResumeCall(callid); // resume the call break; case "6": DoMute(true); // mute the headset (note for legacy wireless products, audio link must be active) break; case "7": DoMute(false); // unmute the headset (note for legacy wireless products, audio link must be active) break; case "8": DoTerminateCall(callid); // inform Plantronics my app has now terminated the call break; case "0": quit = true; break; default: System.out.println("Unrecognised menu choice."); break; } } DoStopEventsListener(); // stop events polling DoReleaseDevice(); // Cleanup the Plantronics REST API } private static void ShowMenu() { System.out.println(); System.out.println("plt sample menu"); System.out.println("--"); System.out.println("1 - ring/incoming call"); System.out.println("2 - outgoing call"); System.out.println("3 - answer call"); System.out.println("4 - hold call"); System.out.println("5 - resume call"); System.out.println("6 - mute call"); System.out.println("7 - unmute call"); System.out.println("8 - end call"); System.out.println("0 - quit"); System.out.println(); System.out.print("Type a command> "); } private static String ReadCommand() { BufferedReader buffer=new BufferedReader(new InputStreamReader(System.in)); String line = ""; try { line = buffer.readLine(); } catch (IOException ex) { Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex); } return line; } public static void DoShowDeviceInfo() { // get info about attached device (if any) RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/DeviceServices/Info" ); } // Connect to the Plantronics REST API public static void DoAttachDevice() { // Connect to the Plantronics REST API (attach device session) String tmpResult = RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/DeviceServices/Attach?uid=0123456789" ); int pos = tmpResult.indexOf("\"Result\":\""); if (pos>-1) { tmpResult = tmpResult.substring(pos+10); pos = tmpResult.indexOf("\""); if (pos>-1) { tmpResult = tmpResult.substring(0, pos); System.out.println("Session id is: " + tmpResult); sessionid = tmpResult; } try { Thread.sleep(250); } catch (InterruptedException ex) { Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex); } if (sessionid.length()>0) { // Register Spokes plugin (Plantronics API application session) RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/SessionManager/Register?name=My%20Java%20Plugin" ); pluginRegistered = true; } } else { System.out.println("Error: Connecting to Device failed, no Result/session detected in response"); } } // Cleanup the Plantronics REST API public static void DoReleaseDevice() { // Unregister Spokes plugin (Plantronics API application session) RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/SessionManager/UnRegister?name=My%20Java%20Plugin" ); pluginRegistered = false; try { Thread.sleep(250); } catch (InterruptedException ex) { Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex); } if (sessionid.length()>0) { // Disconnect from the Plantronics REST API (release device session) RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/DeviceServices/Release?sess=" + sessionid ); sessionid = ""; } } private static void DoIncomingCall(int callid, String caller_name) { if (sessionid.length()>0 && pluginRegistered) { // inform Plantronics my app has an incoming (ringing) call RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/IncomingCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D&contact=%7B%22Name%22%3A%22" + caller_name + "%22%7D&tones=Unknown&route=ToHeadset" ); } else { System.out.println("Error: You need to Attach device first"); } } private static void DoOutgoingCall(int callid, String caller_name) { if (sessionid.length()>0 && pluginRegistered) { // inform Plantronics my app has an outgoing call RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/OutgoingCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D&contact=%7B%22Name%22%3A%22" + caller_name + "%22%7D&route=ToHeadset" ); } else { System.out.println("Error: You need to Attach device first"); } } private static void DoTerminateCall(int callid) { if (sessionid.length()>0 && pluginRegistered) { // inform Plantronics my app has now terminated the call RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/TerminateCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D" ); } else { System.out.println("Error: You need to Attach device first"); } } private static void DoAnswerCall(int callid) { if (sessionid.length()>0 && pluginRegistered) { // inform Plantronics my app has now answered an incoming (ringing) call RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/AnswerCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D" ); } else { System.out.println("Error: You need to Attach device"); } } private static void DoHoldCall(int callid) { if (sessionid.length()>0 && pluginRegistered) { // place call on hold RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/HoldCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D" ); } else { System.out.println("Error: You need to Attach device"); } } private static void DoResumeCall(int callid) { if (sessionid.length()>0 && pluginRegistered) { // resume the call RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/ResumeCall?name=My%20Java%20Plugin&callID=%7B%22Id%22%3A%22" + callid + "%22%7D" ); } else { System.out.println("Error: You need to Attach device"); } } private static void DoMute(boolean muted) { if (sessionid.length()>0 && pluginRegistered) { // mute/un-mute the headset (note for legacy wireless products, audio link must be active) RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/MuteCall?name=My%20Java%20Plugin&muted=" + muted ); } else { System.out.println("Error: You need to Attach device first"); } } private static void DoStartEventsListener(String sessid) { if (eventslistener==null) { eventslistener = new EventsListenerThread(sessid, "My%20Java%20Plugin"); eventslistener.start(); } } private static void DoStopEventsListener() { if (eventslistener!=null) { eventslistener.shutdown(); eventslistener = null; } } private static void DoGetCallManagerState() { if (sessionid.length()>0 && pluginRegistered) { // query current Plantronics API call manager state RESTConvenienceClass.SendRESTCommand( BaseURL + "Spokes/CallServices/CallManagerState" ); } else { System.out.println("Error: You need to Attach device first"); } } } class EventsListenerThread extends Thread { private Boolean quit = false; private Boolean deviceAttached = true; private String sessid = ""; private String plugin_name; private static String BaseURL = "http://127.0.0.1:32017/"; //private static String BaseURL = "https://127.0.0.1:32018/"; public EventsListenerThread(String sessid, String plugin_name) { this.sessid = sessid; this.plugin_name = plugin_name; if (sessid.length()==0) { deviceAttached = false; } } public void run () { System.out.println("Events Listener Starting"); while (!quit) { Sleep(1800); if (!quit) { System.out.println(); System.out.println(); if (!deviceAttached) { System.out.println("-- POLLING FOR DEVICE RE-ATTACH --"); PlantronicsRESTDemo.DoAttachDevice(); if (PlantronicsRESTDemo.sessionid.length()>0) { sessid = PlantronicsRESTDemo.sessionid; deviceAttached = true; PlantronicsRESTDemo.DoShowDeviceInfo(); } } else { System.out.println("-- POLLING FOR EVENTS --"); // deviceEvents informs us of a variety of Plantronics device state changes // SPECIAL CASE device removal - with the REST API, when device is removed // polling for device events will return error Error of type Invalid session id String deviceEvents = RESTConvenienceClass.SendRESTCommandWithDebugPrompt( BaseURL + "Spokes/DeviceServices/Events?sess=" + sessid + "&queue=0" , "DEVICE EVENTS", true ); if(deviceEvents.contains("Invalid session id") || deviceEvents.contains("Empty session id")) { System.out.println("-- ** DEVICE DETACHED / SESSION INVALID ** --"); deviceAttached = false; PlantronicsRESTDemo.sessionid = ""; PlantronicsRESTDemo.DoReleaseDevice(); } Sleep(200); if (!quit && deviceAttached) { // session Call Events informs us the calling state has changed, for example user as answered/terminated a call // using headset buttons - this event should be used in my app to actually connect/terminate the call! String callEvents = RESTConvenienceClass.SendRESTCommandWithDebugPrompt( BaseURL + "Spokes/CallServices/CallEvents?name=My%20Java%20Plugin" , "CALL EVENTS", true ); ParseCallEvents(callEvents); } } } } System.out.println("Events Listener Stopping"); } private void ParseCallEvents(String callEvents) { if (callEvents.contains("[") || callEvents.contains("\"Action\":")) { callEvents = callEvents.substring(callEvents.indexOf("[")); callEvents = callEvents.substring(0, callEvents.indexOf("]")); List<String> actions = Arrays.asList(callEvents.split("\"Action\":")); String callId; for(Iterator<String> i = actions.iterator(); i.hasNext(); ) { String item = i.next(); int pos = item.indexOf(","); if (pos>-1) { callId = item.substring(item.indexOf("\"Id\":") + 5); callId = callId.substring(0, callId.indexOf(",")); System.out.println("CallState: " + RESTConvenienceClass.eCallState.get(item.substring(0, pos)) + ", " + "Call ID: " + callId); } } } } public void shutdown() { quit = true; } private void Sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException ex) { Logger.getLogger(EventsListenerThread.class.getName()).log(Level.SEVERE, null, ex); } } } class RESTConvenienceClass { public static String SendRESTCommand(String restcommand) { return SendRESTCommandWithDebugPrompt(restcommand, "RESULT", true); } public static String SendRESTCommandWithDebugPrompt(String restcommand, String debugprompt, Boolean showsent) { String result = ""; if (showsent) System.out.println("Sending REST Command: " + restcommand); try { result = getHTML(restcommand); System.out.println(debugprompt + " = " + result); } catch (Exception ex) { Logger.getLogger(PlantronicsRESTDemo.class.getName()).log(Level.SEVERE, null, ex); } return result; } // thanks: http://stackoverflow.com/questions/1485708/how-do-i-do-a-http-get-in-java public static String getHTML(String urlToRead) throws Exception { StringBuilder result = new StringBuilder(); URL url = new URL(urlToRead); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while ((line = rd.readLine()) != null) { result.append(line); } rd.close(); return result.toString(); } public static Map<String, String> eCallState = new HashMap<String, String>() {{ put("0", "CALL_STATE_UNKNOWN"); put("1", "CALL_STATE_ACCEPT_CALL"); put("2", "CALL_STATE_TERMINATE_CALL"); put("3", "CALL_STATE_HOLD_CALL"); put("4", "CALL_STATE_RESUME_CALL"); put("5", "CALL_STATE_FLASH"); put("6", "CALL_STATE_CALL_IN_PROGRESS"); put("7", "CALL_STATE_CALL_RINGING"); put("8", "CALL_STATE_CALL_ENDED"); put("9", "CALL_STATE_TRANSFER_TO_HEADSET"); put("10", "CALL_STATE_TRANSFER_TO_SPEAKER"); put("11", "CALL_STATE_MUTEON"); put("12", "CALL_STATE_MUTEOFF"); put("13", "CALL_STATE_MOBILE_CALL_RINGING"); put("14", "CALL_STATE_MOBILE_CALL_IN_PROGRESS"); put("15", "CALL_STATE_MOBILE_CALL_ENDED"); put("16", "CALL_STATE_DON"); put("17", "CALL_STATE_DOFF"); put("18", "CALL_STATE_CALL_IDLE"); put("19", "CALL_STATE_PLAY"); put("20", "CALL_STATE_PAUSE"); put("21", "CALL_STATE_STOP"); put("22", "CALL_STATE_DTMF_KEY"); put("23", "CALL_STATE_REJECT_CALL"); put("24", "CALL_STATE_MAKE_CALL"); put("25", "CALL_STATE_HOOK"); put("26", "CALL_STATE_HOOK_IDLE"); put("27", "CALL_STATE_HOOK_DOCKED"); put("28", "CALL_STATE_HOOK_UNDOCKED"); put("29", "CALL_STATE_BASE_EVENT"); put("30", "CALL_STATE_CALL_ANSWERED_AND_ENDED"); put("31", "CALL_STATE_CALL_UNANSWERED_AND_ENDED"); put("32", "CALL_STATE_DEVICE_CHANGE"); put("33", "CALL_STATE_DEVICE_ARRIVED"); put("34", "CALL_STATE_DEVICE_REMOVED"); }}; }
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <!-- Plantronics JavaScript Sample.html : JavaScript sample demonstrating basic call control. Try a *live* demo now! Visit: https://pltdev.github.io/ NOTE: This sample has a pre-requisite on spokes.js (part of the Plantronics SDK located here: C:\Program Files (x86)\Plantronics\Spokes3G SDK\Samples / https://pltdev.github.io/spokes.js) And jQuery version 1.7.2 (https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js) And a convenience function "print_r" for debug (located: https://pltdev.github.io/util.js) --> <head> <meta charset="utf-8" /> <title>Plantronics JavaScript Sample</title> </head> <!-- Connect / disconnect with Plantronics REST API --> <body onload="connectToPlantronics()" onbeforeunload="disconnectFromPlantronics()"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script src="https://pltdev.github.io/spokes.js"></script> <!-- Plantronics API interface --> <script src="https://pltdev.github.io/util.js"></script> <!-- Main page --> <h1>Plantronics JavaScript Sample</h1> Pre-requisite, install Plantronics Hub from: <a href="http://www.plantronics.com/software" target="_new">www.plantronics.com/software</a> <p>Note Firefox users: If you get "Error connecting to Plantronics Hub." then visit this URL: <a href="https://127.0.0.1:32018/Spokes/DeviceServices/Info" target="_new">https://127.0.0.1:32018/Spokes/DeviceServices/Info</a> and click Advanced > Add Exception... to add a security exception to allow the connection.</p> <p> <b>plt sample menu<br /> --</b><br /> <button id="incoming_call">ring/incoming call</button><br /> <button id="outgoing_call">outgoing call</button><br /> <button id="answer_call">answer call</button><br /> <button id="hold_call">hold call</button><br /> <button id="resume_call">resume call</button><br /> <button id="mute_call">mute call</button><br /> <button id="unmute_call">unmute call</button><br /> <button id="end_call">end call</button> </p> <button id="clear_log">Clear Log</button> <div id="log"><b>Log:</b></div> <script type="text/javascript"> var spokes; // global Spokes object, initialised when this page is loaded (onload) var plugin_registered = false; var plugin_name = "Hello World JavaScript Plugin"; var callid = 0; var deviceAttached = true; var pollRate = 2000; var previousPollRate = 2000; // start polling the API for device events to to reconnect to API (in the event of connection failure)... var run = setInterval(pollDeviceEventsOrReconnect, pollRate); // Sample menu command implementations: $('#clear_log').click(function () { log.innerHTML = "<b>Log:</b>"; }); // inform Plantronics my app has an incoming (ringing) call $('#incoming_call').click(function () { callid = callid + 1; call_id = callid; appendLog("<font color=\"#00FF00\">Initiating make call command, call id = " + callid.toString() + "</font>"); spokes.Plugin.incomingCall(plugin_name, getCallId(callid), getContact("Dummy Contact"), "Unknown", "ToHeadset", function (result) { showCallStatus(result); }); }); // inform Plantronics my app has an outgoing call $('#outgoing_call').click(function () { callid = callid + 1; call_id = callid; appendLog("<font color=\"#00FF00\">Initiating make call command, call id = " + callid.toString() + "</font>"); spokes.Plugin.outgoingCall(plugin_name, getCallId(callid), getContact("Dummy Contact"), "ToHeadset", function (result) { showCallStatus(result); }); }); // inform Plantronics my app has now answered an incoming (ringing) call $('#answer_call').click(function () { appendLog("<font color=\"#00FF00\">Answering call command, call id = " + callid.toString() + "</font>"); spokes.Plugin.answerCall(plugin_name, getCallId(callid), function (result) { showCallStatus(result); }); }); // place call on hold $('#hold_call').click(function () { appendLog("<font color=\"#00FF00\">Hold call command, call id = " + callid.toString() + "</font>"); spokes.Plugin.holdCall(plugin_name, getCallId(callid), function (result) { showCallStatus(result); }); }); // resume the call $('#resume_call').click(function () { appendLog("<font color=\"#00FF00\">Resume call command, call id = " + callid.toString() + "</font>"); spokes.Plugin.resumeCall(plugin_name, getCallId(callid), function (result) { showCallStatus(result); }); }); // mute the headset (note for legacy wireless products, audio link must be active) $('#mute_call').click(function () { appendLog("<font color=\"#00FF00\">Muting call</font>"); spokes.Plugin.muteCall(plugin_name, true, function (result) { showCallStatus(result); }); }); // unmute the headset (note for legacy wireless products, audio link must be active) $('#unmute_call').click(function () { appendLog("<font color=\"#00FF00\">Unmuting call</font>"); spokes.Plugin.muteCall(plugin_name, false, function (result) { showCallStatus(result); }); }); // inform Plantronics my app has now terminated the call $('#end_call').click(function () { appendLog("<font color=\"#00FF00\">Ending call, call id = " + callid.toString() + "</font>"); spokes.Plugin.terminateCall(plugin_name, getCallId(callid), function (result) { showCallStatus(result); }); }); // Convenience functions to create call and contact structures used for IncomingCall/OutgoingCall functions function getCallId(callid) { return new SpokesCallId({ Id: '' + callid } ); } function getContact(contactname) { return new SpokesContact({ Name: contactname }); } // displays status of call commands function showCallStatus(result, toJson) { if (result.isError) { appendLog("Error: " + result.Err.Description); } else { if (toJson) appendLog(JSON.stringify(result.Result)) else appendLog("Success."); } }; function appendLog(str) { log.innerHTML = log.innerHTML + "<br />" + str; } function connectToPlantronics() { // Connect to the Plantronics REST API spokes = new Spokes("https://127.0.0.1:32018/Spokes"); // get info about attached device (if any) spokes.Device.deviceInfo(function (result) { if (!result.isError) { if (result.Result /*[0]*/ != null) { // Log Spokes active device found (first in list returned, index 0) appendLog("Device found = " + result.Result /*[0]*/.ProductName + ", id = " + result.Result /*[0]*/.Uid); deviceAttached = true; // attach to the device, provide a callback function for the result spokes.Device.attach(result.Result /*[0]*/.Uid, deviceAttachedCallback); } else { appendLog("Error: Device was null on connecting to Spokes. Is there a Plantronics device connected?"); deviceAttached = false; } pollRate = 2000; // waiting for device events now, force faster polling rate to start now (if applicable) if (previousPollRate == 10000) { var previousPollRate = 2000; // start polling the device and call state events... var run = setInterval(pollDeviceEventsOrReconnect, pollRate); } } else { if (result.Err.Description === "There are no supported devices") { appendLog("Please attach a Plantronics headset to the PC."); } else { appendLog("Error connecting to Plantronics Hub. (Have you installed and run Plantronics Hub from <a href=\"http://www.plantronics.com/software\" target=\"_new\">www.plantronics.com/software</a>, or " + "are you Firefox user and getting \"Error connecting to Plantronics Hub.\"? If so visit this URL: <a href=\"https://127.0.0.1:32018/Spokes/DeviceServices/Info\" target=\"_new\">" + "https://127.0.0.1:32018/Spokes/DeviceServices/Info</a> and click Advanced > Add Exception... to add a security exception to allow the connection."); pollRate = 10000; // slow down polling rate while we are waiting for Hub to be running } } }); } //Callback to receive result of device attach. If successful register a plugin (Plantronics API application session) function deviceAttachedCallback(session) { if (session.isError || !spokes.Device.isAttached) { appendLog("Session Registration Error"); deviceAttached = false; disconnectFromPlantronics(); } else { appendLog("Session ID: " + session.Result); registerPlugin(); // register a plugin (Plantronics API application session) } } function setPluginActive() { //Set plugin active status to true spokes.Plugin.isActive(plugin_name, true, function (result) { if (!result.isError) { // plugin registered and active. Show UI. plugin_registered = true; appendLog("Plugin \"" + plugin_name + "\" registered successfully."); } else { appendLog("Error checking if plugin is active: " + result.Err.Description); } }); } // Register a Spokes Plugin (Plantronics API application session) to get access to Call Services, Device and Call events function registerPlugin() { if (!plugin_registered) { spokes.Plugin.register(plugin_name, function (result) { if (!result.isError) { setPluginActive(); } else { appendLog("Info: registering plugin: " + result.Err.Description); if (result.Err.Description === "Plugin exists") { setPluginActive(); } else { deviceAttached = false; disconnectFromPlantronics(); } } }); } } // Unregister Spokes plugin (Plantronics API application session) function unregisterPlugin() { spokes.Plugin.unRegister(plugin_name); plugin_registered = false; appendLog("Plugin un-registered."); } // Cleanup the Plantronics REST API function disconnectFromPlantronics() { unregisterPlugin(); spokes.Device.release(function (result) { if (!result.isError) { appendLog("Released device"); } else { appendLog("Error releasing device"); } appendLog("Disconnected from Spokes"); }); } // Function to perform device and call event polling if we are connected to Hub, or else attempt to reconnect to Hub function pollDeviceEventsOrReconnect() { // supports variable poll rate, 2000ms waiting for a device, 10000ms waiting for Hub to be running if (previousPollRate != pollRate) { clearInterval(run); previousPollRate = pollRate; run = setInterval(pollDeviceEventsOrReconnect, pollRate); } if (spokes == null || !deviceAttached || !spokes.Device.isAttached) { appendLog("-- POLLING FOR HUB / DEVICE RE-ATTACH --"); connectToPlantronics(); return; } // Poll for device events // informs us of a variety of Plantronics device state changes spokes.Device.events( function (result) { if (result.isError) { appendLog("Error polling for device events: " + result.Err.Description); if (result.Err.Description === "No response. Server appears to be offline.") { pollRate = 10000; appendLog("changing POLL RATE to " + pollRate); } if (result.Err.Description === "Invalid session id" || result.Err.Description === "Empty session id" || result.Err.Description === "No response. Server appears to be offline.") { appendLog("-- ** DEVICE DETACHED / SESSION INVALID ** --"); deviceAttached = false; disconnectFromPlantronics(); } } else { // display list of events collected from REST service if (result.Result.length > 0) { for (var i = 0; i < result.Result.length; i++) { appendLog("<font color=\"#0000FF\">Device Event: " + result.Result[i].Event_Log_Type_Name + ", " + print_r(result.Result[i]) + "</font>"); } } } }); // Poll for call state events (call control events) // informs us the calling state has changed, for example user as answered/terminated a call // using headset buttons - this event should be used in my app to actually connect/terminate the call! spokes.Plugin.callEvents(plugin_name, function (result) { if (result.isError) { appendLog("Error polling for call events: " + result.Err.Description); if (result.Err.Description === "No response. Server appears to be offline.") { pollRate = 10000; appendLog("changing POLL RATE to " + pollRate); } if (result.Err.Description === "Invalid session id" || result.Err.Description === "Empty session id" || result.Err.Description === "No response. Server appears to be offline.") { appendLog("-- ** DEVICE DETACHED / SESSION INVALID ** --"); deviceAttached = false; disconnectFromPlantronics(); } } else { // display list of events collected from REST service if (result.Result.length > 0) { for (var i = 0; i < result.Result.length; i++) { appendLog("<font color=\"#0000FF\">Call Event: " + result.Result[i].Event_Log_Type_Name + ", " + print_r(result.Result[i]) + "</font>"); // Workout the actual call state and call id in question var callState = SessionCallState.Lookup[result.Result[i]["Action"]]; var callId = result.Result[i]["CallId"]["Id"]; appendLog("CallState: " + callState + ", Call ID: " + callId); } } } }); } </script> </body> </html>
// PLTCPlusPlusNativeSample.cpp : C++ Sample demonstrating basic call control. This implementation is linked to Plantronics native Spokes library // Note: With Plantronics SDK installed, ensure the following project configuration: // 1. C/C++ -> General -> Additional Include direcories => // C:/Program Files (x86)/Plantronics/Spokes3G SDK/Include;C:/Program Files/Plantronics/Spokes3G SDK/Include // 2. Linker -> General -> Additional Library Directories => // C:/Program Files (x86)/Plantronics/Spokes3G SDK;C:/Program Files/Plantronics/Spokes3G SDK // 3. Linker -> Input -> Additional Dependancies => // %(AdditionalDependencies);Spokes.lib // **NOTE**: Native Library does not support Manager Pro, Multi-softphone or Multi-device configurations // Please consider REST Service (PC and Mac) or COM Service (PC only) APIs // For more information see: http://developer.plantronics.com/api-overview#second #pragma once #include <windows.h> #include <iostream> #include <sstream> #include <string> #include <iomanip> #include <stdio.h> #include <tchar.h> #include <SDKDDKVer.h> #include "Spokes3G.h" #include "cpp\query_cast.h" using namespace std; bool gGotDevice = false; std::string EnumToString(eCallState val) { std::string str; switch (val) { case CALL_STATE_UNKNOWN: str = "CALL_STATE_UNKNOWN "; break; case CALL_STATE_ACCEPT_CALL: str = "CALL_STATE_ACCEPT_CALL "; break; case CALL_STATE_TERMINATE_CALL: str = " CALL_STATE_TERMINATE_CALL"; break; case CALL_STATE_HOLD_CALL: str = "CALL_STATE_HOLD_CALL "; break; case CALL_STATE_RESUME_CALL: str = " CALL_STATE_RESUME_CALL "; break; case CALL_STATE_FLASH: str = "CALL_STATE_FLASH "; break; case CALL_STATE_CALL_IN_PROGRESS: str = "CALL_STATE_CALL_IN_PROGRESS "; break; case CALL_STATE_CALL_RINGING: str = "CALL_STATE_CALL_RINGING "; break; case CALL_STATE_CALL_ENDED: str = "CALL_STATE_CALL_ENDED "; break; case CALL_STATE_TRANSFER_TO_HEADSET: str = " CALL_STATE_TRANSFER_TO_HEADSET "; break; case CALL_STATE_TRANSFER_TO_SPEAKER: str = "CALL_STATE_TRANSFER_TO_SPEAKER "; break; case CALL_STATE_MUTEON: str = "CALL_STATE_MUTEON "; break; case CALL_STATE_MUTEOFF: str = "CALL_STATE_MUTEOFF "; break; case CALL_STATE_MOBILE_CALL_RINGING: str = " CALL_STATE_MOBILE_CALL_RINGING "; break; case CALL_STATE_MOBILE_CALL_IN_PROGRESS: str = "CALL_STATE_MOBILE_CALL_IN_PROGRESS "; break; case CALL_STATE_MOBILE_CALL_ENDED: str = " CALL_STATE_MOBILE_CALL_ENDED"; break; case CALL_STATE_DON: str = " CALL_STATE_DON "; break; case CALL_STATE_DOFF: str = "CALL_STATE_DOFF "; break; case CALL_STATE_CALL_IDLE: str = " CALL_STATE_CALL_IDLE"; break; case CALL_STATE_PLAY: str = "CALL_STATE_PLAY "; break; case CALL_STATE_PAUSE: str = " CALL_STATE_PAUSE"; break; case CALL_STATE_STOP: str = "CALL_STATE_STOP "; break; case CALL_STATE_DTMF_KEY: str = " CALL_STATE_DTMF_KEY "; break; case CALL_STATE_REJECT_CALL: str = "CALL_STATE_REJECT_CALL "; break; case CALL_STATE_MAKE_CALL: str = " CALL_STATE_MAKE_CALL "; break; // Information only. case CALL_STATE_HOOK: str = " CALL_STATE_HOOK"; break; case CALL_STATE_HOOK_IDLE: str = " CALL_STATE_HOOK_IDLE "; break; case CALL_STATE_HOOK_DOCKED: str = " CALL_STATE_HOOK_DOCKED"; break; case CALL_STATE_HOOK_UNDOCKED: str = " CALL_STATE_HOOK_UNDOCKED"; break; case CALL_STATE_BASE_EVENT: str = "CALL_STATE_BASE_EVENT "; break; case CALL_STATE_CALL_ANSWERED_AND_ENDED: str = "CALL_STATE_CALL_ANSWERED_AND_ENDED "; break; case CALL_STATE_CALL_UNANSWERED_AND_ENDED: str = "CALL_STATE_CALL_UNANSWERED_AND_ENDED "; break; case CALL_STATE_DEVICE_ARRIVED: str = "CALL_STATE_DEVICE_ARRIVED "; break; case CALL_STATE_DEVICE_REMOVED: str = "CALL_STATE_DEVICE_REMOVED "; break; case CALL_STATE_DEVICE_CHANGE: str = "CALL_STATE_DEVICE_CHANGE "; break; default:return "CALL_STATE_UNKNOWN"; }; return str; } std::string EnumToString(eDeviceState val) { std::string str; switch (val) { case DEV_STATE_ADDED: str = " DEV_STATE_ADDED"; break; case DEV_STATE_REMOVED: str = " DEV_STATE_REMOVED"; break; default: str = " DEV_STATE_UNKNOWN"; } return str; } void PrintDeviceDetails(IDevice* device) { bool muted; IDeviceListener* myDeviceListener = nullptr; DMResult res = device->getDeviceListener(&myDeviceListener); if (res != DMResult::DM_RESULT_SUCCESS) { std::cout << "Device is not attached / unable to hook up to device listener events" << std::endl; return; } std::cout << "Successfully hooked to device listener events" << std::endl; wchar_t buffer1[1024]; device->getProductName(buffer1, 1024); int32_t pid = device->getProductID(); std::wcout << "Device attached: " << buffer1; std::wcout << L", Product ID = " << std::uppercase << std::hex << std::setfill(L'0') << std::setw(4) << pid << std::endl; // Obtain initial device microphone mute state res = myDeviceListener->getMute(muted); std::cout << "Device mute state: muted = "; (muted == true) ? std::cout << "True" : std::cout << "False"; std::cout << std::endl; return; } // Call Object class Call : public ICall { private: int32_t m_id = 0; public: virtual int32_t getID() { return m_id; } virtual void setID(int32_t id) { m_id = id; } virtual int32_t getConferenceID() { return 0; } virtual void setConferenceID(int32_t id) { /*no op*/ } virtual bool getInConference() { return false; } virtual void setInConference(bool bConference) { /*no op*/ } void operator delete(void *p) { ::operator delete(p); } ~Call() { } }; ICall* GetCall(Call& call) { std::string strCallId; std::cout << "Enter Call Id: "; std::getline(std::cin, strCallId); int32_t id; std::stringstream myStream(strCallId); myStream >> id; call.setID(id); return &call; } ICall* GetCall(Call& call, int id) { call.setID(id); return &call; } // Contact Object class Contact : public IContact { private: int32_t m_id = 0; wstring m_name; wstring m_friendlyName; wstring m_sipUri; wstring m_email; wstring m_phone; wstring m_workPhone; wstring m_mobilePhone; wstring m_homePhone; public: virtual const wchar_t* getName() const { return m_name.c_str(); } virtual void setName(const wchar_t* pName) { m_name = pName; } virtual const wchar_t* getFriendlyName() const { return m_friendlyName.c_str(); } virtual void setFriendlyName(const wchar_t* pFName) { m_friendlyName = pFName; } virtual int32_t getID() const { return m_id; } virtual void setID(int32_t id) { m_id = id; } virtual const wchar_t* getSipUri() const { return m_sipUri.c_str(); } virtual void setSipUri(const wchar_t* pSip) { m_sipUri = pSip; } virtual const wchar_t* getPhone() const { return m_phone.c_str(); } virtual void setPhone(const wchar_t* pPhone) { m_phone = pPhone; } virtual const wchar_t* getEmail() const { return m_email.c_str(); } virtual void setEmail(const wchar_t* pEmail) { m_email = pEmail; } virtual const wchar_t* getWorkPhone() const { return m_workPhone.c_str(); } virtual void setWorkPhone(const wchar_t* pWPhone) { m_workPhone = pWPhone; } virtual const wchar_t* getMobilePhone() const { return m_mobilePhone.c_str(); } virtual void setMobilePhone(const wchar_t* pMPhone) { m_mobilePhone = pMPhone; } virtual const wchar_t* getHomePhone() const { return m_homePhone.c_str(); } virtual void setHomePhone(const wchar_t* pHPhone) { m_homePhone = pHPhone; } void operator delete(void *p) { ::operator delete(p); } ~Contact() { } }; IContact* GetContact() { std::string strContactName; std::cout << "Enter contact name: "; std::getline(std::cin, strContactName); wstring wName(strContactName.begin(), strContactName.end()); std::cout << "Name:" << strContactName << std::endl; IContact* contact = nullptr; // getContact( &contact ); // contact->setName( wName.c_str() ); return contact; } IContact* GetContact(Contact& contact, std::string name) { std::wstring wsTmp(name.begin(), name.end()); contact.setName(wsTmp.c_str()); return &contact; } Call call; Contact contact; // create sink for receiving session events class CallEventSink : public ICallEvents { private: ISession* m_session = nullptr; public: CallEventSink() {} CallEventSink(ISession* session) : m_session(session) { m_session->registerCallback(this); } virtual ~CallEventSink() { m_session->unregisterCallback(this); } virtual bool OnCallStateChanged(CallStateEventArgs const& pcscArgs) { std::string state = EnumToString(pcscArgs.callState); //cout << "OnCallStateChanged Session event " << state.c_str() << endl; return true; } virtual bool OnCallRequest(CallRequestEventArgs const& pcscArgs) { cout << "OnCallRequest Session event" << endl; return true; } void operator delete(void *p) { ::operator delete(p); } }; // create sink for receiving session manager events class SessionManagerEventSink : public ISessionManagerEvents { private: ISessionManager* m_sessionManager = nullptr; public: SessionManagerEventSink() {} SessionManagerEventSink(ISessionManager* sessionManager) : m_sessionManager(sessionManager) { m_sessionManager->registerCallback(this); } virtual ~SessionManagerEventSink() { m_sessionManager->unregisterCallback(this); gGotDevice = false; } virtual bool OnCallStateChanged(CallStateEventArgs const& cseArgs) { int32_t callId = call.getID(); std::string state = EnumToString(cseArgs.callState); std::cout << "Call State Changed: callid=" << callId << " new state=" << state.c_str() << endl; return true; } virtual bool OnDeviceStateChanged(DeviceStateEventArgs const& devArgs) { eDeviceState devState = devArgs.deviceState; std::string state = EnumToString(devState); cout << "OnDeviceStateChanged: " << state.c_str() << endl; gGotDevice = false; // schedule a device re-attach attempt next time around the main menu loop if (devState == eDeviceState::DEV_STATE_ADDED) { std::cout << "Press Enter to attach this device." << std::endl; } return true; } void operator delete(void *p) { ::operator delete(p); } }; void ShowMenu() { std::cout << std::endl; std::cout << "plt sample menu" << std::endl; std::cout << "--" << std::endl; std::cout << "1 - ring/incoming call" << std::endl; std::cout << "2 - outgoing call" << std::endl; std::cout << "3 - answer call" << std::endl; std::cout << "4 - hold call" << std::endl; std::cout << "5 - resume call" << std::endl; std::cout << "6 - mute call" << std::endl; std::cout << "7 - unmute call" << std::endl; std::cout << "8 - end call" << std::endl; std::cout << "0 - quit" << std::endl; std::cout << std::endl; std::cout << "> "; } int _tmain(int argc, _TCHAR* argv[]) { bool quit = false; std::string input; int cmd = -1; int _callid = 0; // variable to track call id between my app and Plantronics InitSpokesRuntime(); cout << "C++ Plantronics Native API Sample" << std::endl; // create session manager ISessionManager *sessionManager = nullptr; if (SM_RESULT_SUCCESS == getSessionManager(&sessionManager)) { // create session ISession* session = nullptr; if (SM_RESULT_SUCCESS == sessionManager->registerSession(L"Sample client", &session)) { IDevice* activeDevice = nullptr; SMResult res = session->getActiveDevice(&activeDevice); // always succeeds (since getActiveDevice() gets a proxy & retruns SM_RESULT_SUCCESS) IUserPreference* userPreference = nullptr; if (SM_RESULT_SUCCESS == sessionManager->getUserPreference(&userPreference)) { // set this session as default one for receiving CallRequest events int32_t pluginId; session->getPluginId(&pluginId); userPreference->setDefaultSoftphone(pluginId); } // get call command ICallCommand* callCommand = nullptr; if (SM_RESULT_SUCCESS == session->getCallCommand(&callCommand)) { // sink to all call events unique_ptr<CallEventSink> callEvents(new CallEventSink(session)); //auto callEvents = std::make_unique<CallEventSink>(session); unique_ptr<SessionManagerEventSink> sessionManagerEvents(new SessionManagerEventSink(sessionManager)); //auto sessionManagerEvents = std::make_unique<SessionManagerEventSink>(sessionManager); // enter in command loop while (!quit) { // If we have not attached to device (or device was removed/added), then attempt to attach to a device if (!gGotDevice) { std::cout << "Attempting device attach" << std::endl; if (true == activeDevice->isAttached()) { gGotDevice = true; PrintDeviceDetails(activeDevice); } else { std::cout << "Unable to retrieve/hook to active device" << std::endl; } } ShowMenu(); std::getline(std::cin, input); // Convert from string to number safely. std::stringstream inputStringStream(input); if (!(inputStringStream >> cmd)) { cmd = -1; // check if device is attached and prompt error if it is if (gGotDevice == true) std::cout << "Invalid input, please try again" << std::endl; } switch (cmd) { case 1: _callid++; callCommand->incomingCall(GetCall(call, _callid), // inform Plantronics my app has an incoming (ringing) call GetContact(contact, "Bob%20Smith"), RING_TONE_UNKNOWN, eAudioRoute::AUDIO_ROUTE_TO_HEADSET); break; case 2: _callid++; callCommand->outgoingCall(GetCall(call, _callid), // inform Plantronics my app has an outgoing call GetContact(contact, "Bob%20Smith"), eAudioRoute::AUDIO_ROUTE_TO_HEADSET); break; case 3: callCommand->answeredCall(GetCall(call, _callid)); // inform Plantronics my app has now answered an incoming (ringing) call break; case 4: callCommand->holdCall(GetCall(call, _callid)); // place call on hold break; case 5: callCommand->resumeCall(GetCall(call, _callid)); // resume the call break; case 6: callCommand->muteCall(true); // mute the headset (note for wireless products, audio link must be active) break; case 7: callCommand->muteCall(false); // unmute the headset (note for wireless products, audio link must be active) break; case 8: callCommand->terminateCall(GetCall(call, _callid)); // inform Plantronics my app has now terminated the call break; case 0: quit = true; break; default: if (gGotDevice == true) std::cout << "Unrecognised menu choice, please try again." << std::endl; break; } Sleep(250); } input = ""; activeDevice->Release(); } } sessionManager->unregisterSession(session); session->Release(); } ShutDownSpokesRuntime(); return 0; }