mirror of
https://github.com/chibicitiberiu/rainmeter-studio.git
synced 2024-02-24 04:33:31 +00:00
635 lines
12 KiB
C++
635 lines
12 KiB
C++
/*
|
|
Copyright (C) 2011 Birunthan Mohanathas (www.poiru.net)
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "StdAfx.h"
|
|
#include "PlayerITunes.h"
|
|
|
|
Player* PlayerITunes::c_Player = nullptr;
|
|
extern HINSTANCE g_Instance;
|
|
|
|
/*
|
|
** Constructor.
|
|
**
|
|
*/
|
|
PlayerITunes::CEventHandler::CEventHandler(PlayerITunes* player) :
|
|
m_Player(player),
|
|
m_RefCount(),
|
|
m_ConnectionPoint(),
|
|
m_ConnectionCookie()
|
|
{
|
|
IConnectionPointContainer* icpc;
|
|
m_Player->m_iTunes->QueryInterface(IID_IConnectionPointContainer, (void**)&icpc);
|
|
icpc->FindConnectionPoint(DIID__IiTunesEvents, &m_ConnectionPoint);
|
|
m_ConnectionPoint->Advise(this, &m_ConnectionCookie);
|
|
icpc->Release();
|
|
}
|
|
|
|
/*
|
|
** Destructor.
|
|
**
|
|
*/
|
|
PlayerITunes::CEventHandler::~CEventHandler()
|
|
{
|
|
if (m_ConnectionPoint)
|
|
{
|
|
m_ConnectionPoint->Unadvise(m_ConnectionCookie);
|
|
m_ConnectionPoint->Release();
|
|
}
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE PlayerITunes::CEventHandler::QueryInterface(REFIID iid, void** ppvObject)
|
|
{
|
|
if (iid == IID_IUnknown || iid == IID_IUnknown || iid == DIID__IiTunesEvents)
|
|
{
|
|
++m_RefCount;
|
|
*ppvObject = this;
|
|
return S_OK;
|
|
}
|
|
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE PlayerITunes::CEventHandler::AddRef()
|
|
{
|
|
return ++m_RefCount;
|
|
}
|
|
|
|
ULONG STDMETHODCALLTYPE PlayerITunes::CEventHandler::Release()
|
|
{
|
|
return --m_RefCount;
|
|
}
|
|
|
|
HRESULT STDMETHODCALLTYPE PlayerITunes::CEventHandler::Invoke(DISPID dispidMember, REFIID, LCID, WORD, DISPPARAMS* dispParams, VARIANT*, EXCEPINFO*, UINT*)
|
|
{
|
|
switch (dispidMember)
|
|
{
|
|
case ITEventDatabaseChanged:
|
|
m_Player->OnDatabaseChange();
|
|
break;
|
|
|
|
case ITEventPlayerPlay:
|
|
m_Player->OnStateChange(true);
|
|
m_Player->OnTrackChange();
|
|
break;
|
|
|
|
case ITEventPlayerStop:
|
|
m_Player->OnStateChange(false);
|
|
break;
|
|
|
|
case ITEventPlayerPlayingTrackChanged:
|
|
m_Player->OnTrackChange();
|
|
break;
|
|
|
|
case ITEventSoundVolumeChanged:
|
|
m_Player->OnVolumeChange(dispParams->rgvarg[0].intVal);
|
|
break;
|
|
|
|
case ITEventAboutToPromptUserToQuit:
|
|
PostMessage(m_Player->m_CallbackWindow, WM_USER, ITEventAboutToPromptUserToQuit, 0);
|
|
SetTimer(m_Player->m_CallbackWindow, TIMER_CHECKACTIVE, 500, nullptr);
|
|
break;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*
|
|
** Constructor.
|
|
**
|
|
*/
|
|
PlayerITunes::PlayerITunes() : Player(),
|
|
m_CallbackWindow(),
|
|
m_LastCheckTime(0),
|
|
m_iTunesActive(false),
|
|
m_iTunes(),
|
|
m_iTunesEvent()
|
|
{
|
|
// Create windows class
|
|
WNDCLASS wc = {0};
|
|
wc.hInstance = g_Instance;
|
|
wc.lpfnWndProc = WndProc;
|
|
wc.lpszClassName = L"NowPlayingITunesClass";
|
|
RegisterClass(&wc);
|
|
|
|
// Create callback window
|
|
m_CallbackWindow = CreateWindow(L"NowPlayingITunesClass",
|
|
L"CallbackWindow",
|
|
WS_DISABLED,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
HWND_MESSAGE,
|
|
nullptr,
|
|
g_Instance,
|
|
this);
|
|
}
|
|
|
|
/*
|
|
** Destructor.
|
|
**
|
|
*/
|
|
PlayerITunes::~PlayerITunes()
|
|
{
|
|
c_Player = nullptr;
|
|
|
|
DestroyWindow(m_CallbackWindow);
|
|
UnregisterClass(L"NowPlayingITunesClass", g_Instance);
|
|
|
|
Uninitialize();
|
|
}
|
|
|
|
/*
|
|
** Creates a shared class object.
|
|
**
|
|
*/
|
|
Player* PlayerITunes::Create()
|
|
{
|
|
if (!c_Player)
|
|
{
|
|
c_Player = new PlayerITunes();
|
|
}
|
|
|
|
return c_Player;
|
|
}
|
|
|
|
/*
|
|
** Initialize iTunes COM interface and event handler.
|
|
**
|
|
*/
|
|
void PlayerITunes::Initialize()
|
|
{
|
|
while (true)
|
|
{
|
|
HRESULT hr = CoCreateInstance(CLSID_iTunesApp, nullptr, CLSCTX_LOCAL_SERVER, IID_IiTunes, (PVOID*)&m_iTunes);
|
|
|
|
if (hr == CO_E_SERVER_EXEC_FAILURE)
|
|
{
|
|
// This seems to happen if there is a modal dialog being shown in iTunes
|
|
// or some other delay has occurred. Retrying should do the trick.
|
|
continue;
|
|
}
|
|
else if (hr != S_OK)
|
|
{
|
|
// Failed to get hold of iTunes instance via COM
|
|
m_iTunes = nullptr;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (m_iTunes)
|
|
{
|
|
m_Initialized = true;
|
|
|
|
// Set up event handler
|
|
m_iTunesEvent = new CEventHandler(this);
|
|
|
|
// Try getting track info and player state
|
|
ITPlayerState state;
|
|
if (SUCCEEDED(m_iTunes->get_PlayerState(&state)))
|
|
{
|
|
if (state == ITPlayerStateStopped)
|
|
{
|
|
// Determine if paused of stopped
|
|
long position;
|
|
m_iTunes->get_PlayerPosition(&position);
|
|
|
|
if (position != 0)
|
|
{
|
|
m_State = STATE_PAUSED;
|
|
OnTrackChange();
|
|
}
|
|
}
|
|
else if (state == ITPlayerStatePlaying)
|
|
{
|
|
m_State = STATE_PLAYING;
|
|
OnTrackChange();
|
|
}
|
|
}
|
|
|
|
long volume;
|
|
m_iTunes->get_SoundVolume(&volume);
|
|
m_Volume = (UINT)volume;
|
|
|
|
OnDatabaseChange();
|
|
}
|
|
else
|
|
{
|
|
m_Initialized = false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Close iTunes COM interface.
|
|
**
|
|
*/
|
|
void PlayerITunes::Uninitialize()
|
|
{
|
|
if (m_Initialized)
|
|
{
|
|
m_Initialized = false;
|
|
ClearData();
|
|
|
|
m_iTunes->Release();
|
|
delete m_iTunesEvent;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Window procedure for the callback window.
|
|
**
|
|
*/
|
|
LRESULT CALLBACK PlayerITunes::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
static PlayerITunes* player;
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_CREATE:
|
|
// Get pointer to the PlayerITunes class from the CreateWindow call
|
|
player = (PlayerITunes*)(((CREATESTRUCT*)lParam)->lpCreateParams);
|
|
return 0;
|
|
|
|
case WM_USER:
|
|
if (wParam == ITEventAboutToPromptUserToQuit)
|
|
{
|
|
// Event handler calls this through a PostMessage when iTunes quits
|
|
player->Uninitialize();
|
|
}
|
|
return 0;
|
|
|
|
case WM_TIMER:
|
|
if (wParam == TIMER_CHECKACTIVE)
|
|
{
|
|
if (!FindWindow(L"iTunesApp", L"iTunes") && !FindWindow(L"iTunes", L"iTunes"))
|
|
{
|
|
player->m_iTunesActive = false;
|
|
KillTimer(hwnd, TIMER_CHECKACTIVE);
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
default:
|
|
return DefWindowProc(hwnd, msg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Try to find iTunes periodically.
|
|
**
|
|
*/
|
|
bool PlayerITunes::CheckWindow()
|
|
{
|
|
DWORD time = GetTickCount();
|
|
if (time - m_LastCheckTime > 5000)
|
|
{
|
|
m_LastCheckTime = time;
|
|
|
|
if ((FindWindow(L"iTunesApp", L"iTunes") || FindWindow(L"iTunes", L"iTunes")) && !m_iTunesActive)
|
|
{
|
|
m_iTunesActive = true;
|
|
Initialize();
|
|
}
|
|
}
|
|
|
|
return m_Initialized;
|
|
}
|
|
|
|
/*
|
|
** Called during each update of the main measure.
|
|
**
|
|
*/
|
|
void PlayerITunes::UpdateData()
|
|
{
|
|
if ((m_Initialized || CheckWindow()) && m_State != STATE_STOPPED)
|
|
{
|
|
long position;
|
|
m_iTunes->get_PlayerPosition(&position);
|
|
m_Position = (UINT)position;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Called by iTunes event handler when the database is changed.
|
|
**
|
|
*/
|
|
void PlayerITunes::OnDatabaseChange()
|
|
{
|
|
// Check the shuffle state. TODO: Find better way
|
|
IITPlaylist* playlist;
|
|
HRESULT hr = m_iTunes->get_CurrentPlaylist(&playlist);
|
|
if (SUCCEEDED(hr) && playlist)
|
|
{
|
|
VARIANT_BOOL shuffle;
|
|
hr = playlist->get_Shuffle(&shuffle);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_Shuffle = (bool)shuffle;
|
|
}
|
|
|
|
playlist->Release();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Called by iTunes event handler on track change.
|
|
**
|
|
*/
|
|
void PlayerITunes::OnTrackChange()
|
|
{
|
|
IITTrack* track;
|
|
HRESULT hr = m_iTunes->get_CurrentTrack(&track);
|
|
if (SUCCEEDED(hr) && track)
|
|
{
|
|
BSTR tmpStr;
|
|
long tmpVal;
|
|
|
|
// Get metadata
|
|
track->get_Artist(&tmpStr);
|
|
tmpStr ? (m_Artist = tmpStr) : m_Artist.clear();
|
|
|
|
track->get_Name(&tmpStr);
|
|
tmpStr ? (m_Title = tmpStr) : m_Title.clear();
|
|
|
|
track->get_Album(&tmpStr);
|
|
tmpStr ? (m_Album = tmpStr) : m_Album.clear();
|
|
|
|
track->get_Duration(&tmpVal);
|
|
m_Duration = (UINT)tmpVal;
|
|
|
|
// Rating is 0 - 100, divide to 0 - 5
|
|
track->get_Rating(&tmpVal);
|
|
tmpVal /= 20L;
|
|
m_Rating = (UINT)tmpVal;
|
|
|
|
track->get_TrackNumber(&tmpVal);
|
|
m_Number = (UINT)tmpVal;
|
|
|
|
track->get_Year(&tmpVal);
|
|
m_Year = (UINT)tmpVal;
|
|
|
|
IITPlaylist* playlist;
|
|
hr = track->get_Playlist(&playlist);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
ITPlaylistRepeatMode repeat;
|
|
hr = playlist->get_SongRepeat(&repeat);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_Repeat = (bool)repeat;
|
|
}
|
|
|
|
playlist->Release();
|
|
}
|
|
|
|
IITFileOrCDTrack* file;
|
|
hr = track->QueryInterface(&file);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
file->get_Location(&tmpStr);
|
|
file->Release();
|
|
if (tmpStr && wcscmp(tmpStr, m_FilePath.c_str()) != 0)
|
|
{
|
|
++m_TrackCount;
|
|
m_FilePath = tmpStr;
|
|
|
|
if (m_Measures & MEASURE_COVER)
|
|
{
|
|
m_CoverPath.clear();
|
|
|
|
// Check for embedded art through iTunes interface
|
|
IITArtworkCollection* artworkCollection;
|
|
hr = track->get_Artwork(&artworkCollection);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
long count;
|
|
artworkCollection->get_Count(&count);
|
|
|
|
if (count > 0)
|
|
{
|
|
IITArtwork* artwork;
|
|
hr = artworkCollection->get_Item(1, &artwork);
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
_bstr_t coverPath = m_TempCoverPath.c_str();
|
|
hr = artwork->SaveArtworkToFile(coverPath);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_CoverPath = m_TempCoverPath;
|
|
}
|
|
|
|
artwork->Release();
|
|
}
|
|
}
|
|
|
|
artworkCollection->Release();
|
|
}
|
|
}
|
|
|
|
if (m_Measures & MEASURE_LYRICS)
|
|
{
|
|
FindLyrics();
|
|
}
|
|
}
|
|
}
|
|
|
|
track->Release();
|
|
}
|
|
else
|
|
{
|
|
ClearData(false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Called by iTunes event handler on player state change.
|
|
**
|
|
*/
|
|
void PlayerITunes::OnStateChange(bool playing)
|
|
{
|
|
if (playing)
|
|
{
|
|
m_State = STATE_PLAYING;
|
|
}
|
|
else
|
|
{
|
|
// Guess if paused or stopped from track time
|
|
m_State = (m_Position == 0) ? STATE_STOPPED : STATE_PAUSED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Called by iTunes event handler on volume change.
|
|
**
|
|
*/
|
|
void PlayerITunes::OnVolumeChange(int volume)
|
|
{
|
|
m_Volume = volume;
|
|
}
|
|
|
|
/*
|
|
** Handles the Pause bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::Pause()
|
|
{
|
|
m_iTunes->Pause();
|
|
}
|
|
|
|
/*
|
|
** Handles the Play bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::Play()
|
|
{
|
|
m_iTunes->Play();
|
|
}
|
|
|
|
/*
|
|
** Handles the Stop bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::Stop()
|
|
{
|
|
m_iTunes->Stop();
|
|
}
|
|
|
|
/*
|
|
** Handles the Next bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::Next()
|
|
{
|
|
m_iTunes->NextTrack();
|
|
}
|
|
|
|
/*
|
|
** Handles the Previous bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::Previous()
|
|
{
|
|
m_iTunes->PreviousTrack();
|
|
}
|
|
|
|
/*
|
|
** Handles the SetPosition bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::SetPosition(int position)
|
|
{
|
|
m_iTunes->put_PlayerPosition((long)position);
|
|
}
|
|
|
|
/*
|
|
** Handles the SetRating bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::SetRating(int rating)
|
|
{
|
|
IITTrack* track;
|
|
HRESULT hr = m_iTunes->get_CurrentTrack(&track);
|
|
if (SUCCEEDED(hr) && track)
|
|
{
|
|
rating *= 20;
|
|
track->put_Rating((long)rating);
|
|
track->Release();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Handles the SetVolume bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::SetVolume(int volume)
|
|
{
|
|
m_iTunes->put_SoundVolume((long)volume);
|
|
}
|
|
|
|
/*
|
|
** Handles the SetShuffle bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::SetShuffle(bool state)
|
|
{
|
|
IITTrack* track;
|
|
HRESULT hr = m_iTunes->get_CurrentTrack(&track);
|
|
if (SUCCEEDED(hr) && track)
|
|
{
|
|
IITPlaylist* playlist;
|
|
hr = track->get_Playlist(&playlist);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_Shuffle = state;
|
|
VARIANT_BOOL shuffle = m_Shuffle ? VARIANT_TRUE : VARIANT_FALSE;
|
|
playlist->put_Shuffle(shuffle);
|
|
|
|
playlist->Release();
|
|
}
|
|
|
|
track->Release();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Handles the SetRepeat bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::SetRepeat(bool state)
|
|
{
|
|
IITTrack* track;
|
|
HRESULT hr = m_iTunes->get_CurrentTrack(&track);
|
|
if (SUCCEEDED(hr) && track)
|
|
{
|
|
IITPlaylist* playlist;
|
|
hr = track->get_Playlist(&playlist);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
m_Repeat = state;
|
|
playlist->put_SongRepeat((ITPlaylistRepeatMode)m_Repeat);
|
|
|
|
playlist->Release();
|
|
}
|
|
|
|
track->Release();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Handles the ClosePlayer bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::ClosePlayer()
|
|
{
|
|
m_iTunes->Quit();
|
|
Uninitialize();
|
|
SetTimer(m_CallbackWindow, TIMER_CHECKACTIVE, 500, nullptr);
|
|
}
|
|
|
|
/*
|
|
** Handles the OpenPlayer bang.
|
|
**
|
|
*/
|
|
void PlayerITunes::OpenPlayer(std::wstring& path)
|
|
{
|
|
ShellExecute(nullptr, L"open", path.empty() ? L"iTunes.exe" : path.c_str(), nullptr, nullptr, SW_SHOW);
|
|
}
|