2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "GUIVisualisationControl.h"
11 #include "GUIComponent.h"
12 #include "GUIInfoManager.h"
13 #include "GUIUserMessages.h"
14 #include "GUIWindowManager.h"
15 #include "ServiceBroker.h"
16 #include "addons/AddonManager.h"
17 #include "addons/Visualization.h"
18 #include "addons/addoninfo/AddonType.h"
19 #include "application/Application.h"
20 #include "application/ApplicationComponents.h"
21 #include "application/ApplicationPlayer.h"
22 #include "cores/AudioEngine/Interfaces/AE.h"
23 #include "filesystem/SpecialProtocol.h"
24 #include "guilib/guiinfo/GUIInfoLabels.h"
25 #include "input/actions/Action.h"
26 #include "input/actions/ActionIDs.h"
27 #include "music/tags/MusicInfoTag.h"
28 #include "settings/AdvancedSettings.h"
29 #include "settings/Settings.h"
30 #include "settings/SettingsComponent.h"
31 #include "utils/URIUtils.h"
32 #include "utils/log.h"
38 constexpr unsigned int MAX_AUDIO_BUFFERS
= 16;
41 CAudioBuffer::CAudioBuffer(int iSize
)
44 m_pBuffer
= new float[iSize
];
47 CAudioBuffer::~CAudioBuffer()
52 const float* CAudioBuffer::Get() const
57 int CAudioBuffer::Size() const
62 void CAudioBuffer::Set(const float* psBuffer
, int iSize
)
67 memcpy(m_pBuffer
, psBuffer
, iSize
* sizeof(float));
68 for (int i
= iSize
; i
< m_iLen
; ++i
)
72 CGUIVisualisationControl::CGUIVisualisationControl(
73 int parentID
, int controlID
, float posX
, float posY
, float width
, float height
)
74 : CGUIControl(parentID
, controlID
, posX
, posY
, width
, height
)
76 ControlType
= GUICONTROL_VISUALISATION
;
79 CGUIVisualisationControl::CGUIVisualisationControl(const CGUIVisualisationControl
& from
)
82 ControlType
= GUICONTROL_VISUALISATION
;
85 std::string
CGUIVisualisationControl::Name()
87 if (m_instance
== nullptr)
89 return m_instance
->Name();
92 bool CGUIVisualisationControl::OnMessage(CGUIMessage
& message
)
96 switch (message
.GetMessage())
98 case GUI_MSG_GET_VISUALISATION
:
99 message
.SetPointer(this);
101 case GUI_MSG_VISUALISATION_RELOAD
:
104 case GUI_MSG_PLAYBACK_STARTED
:
105 m_updateTrack
= true;
111 return CGUIControl::OnMessage(message
);
114 bool CGUIVisualisationControl::OnAction(const CAction
& action
)
116 if (m_alreadyStarted
)
118 switch (action
.GetID())
120 case ACTION_VIS_PRESET_NEXT
:
121 m_instance
->NextPreset();
123 case ACTION_VIS_PRESET_PREV
:
124 m_instance
->PrevPreset();
126 case ACTION_VIS_PRESET_RANDOM
:
127 m_instance
->RandomPreset();
129 case ACTION_VIS_RATE_PRESET_PLUS
:
130 m_instance
->RatePreset(true);
132 case ACTION_VIS_RATE_PRESET_MINUS
:
133 m_instance
->RatePreset(false);
135 case ACTION_VIS_PRESET_LOCK
:
136 m_instance
->LockPreset();
144 return CGUIControl::OnAction(action
);
147 void CGUIVisualisationControl::Process(unsigned int currentTime
, CDirtyRegionList
& dirtyregions
)
149 const auto& components
= CServiceBroker::GetAppComponents();
150 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
151 if (appPlayer
->IsPlayingAudio())
156 if (!m_instance
&& !m_attemptedLoad
)
160 m_attemptedLoad
= true;
162 else if (m_callStart
&& m_instance
)
164 auto& context
= CServiceBroker::GetWinSystem()->GetGfxContext();
166 context
.CaptureStateBlock();
167 if (m_alreadyStarted
)
170 m_alreadyStarted
= false;
173 std::string songTitle
= URIUtils::GetFileName(g_application
.CurrentFile());
174 const MUSIC_INFO::CMusicInfoTag
* tag
=
175 CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
176 if (tag
&& !tag
->GetTitle().empty())
177 songTitle
= tag
->GetTitle();
178 m_alreadyStarted
= m_instance
->Start(m_channels
, m_samplesPerSec
, m_bitsPerSample
, songTitle
);
179 context
.ApplyStateBlock();
181 m_updateTrack
= true;
183 else if (m_updateTrack
)
185 /* Initial update of currently processed track */
187 m_updateTrack
= false;
190 if (m_instance
&& m_instance
->IsDirty())
194 CGUIControl::Process(currentTime
, dirtyregions
);
197 void CGUIVisualisationControl::Render()
199 if (m_instance
&& m_alreadyStarted
)
201 auto& context
= CServiceBroker::GetWinSystem()->GetGfxContext();
204 * set the viewport - note: We currently don't have any control over how
205 * the addon renders, so the best we can do is attempt to define
208 context
.SetViewPort(m_posX
, m_posY
, m_width
, m_height
);
209 context
.CaptureStateBlock();
210 m_instance
->Render();
211 context
.ApplyStateBlock();
212 context
.RestoreViewPort();
215 CGUIControl::Render();
218 void CGUIVisualisationControl::UpdateVisibility(const CGUIListItem
* item
/* = nullptr*/)
220 // if made invisible, start timer, only free addonptr after
221 // some period, configurable by window class
222 CGUIControl::UpdateVisibility(item
);
223 if (!IsVisible() && m_attemptedLoad
)
227 bool CGUIVisualisationControl::CanFocusFromPoint(const CPoint
& point
) const
228 { // mouse is allowed to focus this control, but it doesn't actually receive focus
229 return IsVisible() && HitTest(point
);
232 void CGUIVisualisationControl::FreeResources(bool immediately
)
234 DeInitVisualization();
236 CGUIControl::FreeResources(immediately
);
238 CLog::Log(LOGDEBUG
, "FreeVisualisation() done");
241 void CGUIVisualisationControl::OnInitialize(int channels
, int samplesPerSec
, int bitsPerSample
)
243 m_channels
= channels
;
244 m_samplesPerSec
= samplesPerSec
;
245 m_bitsPerSample
= bitsPerSample
;
249 void CGUIVisualisationControl::OnAudioData(const float* audioData
, unsigned int audioDataLength
)
251 if (!m_instance
|| !m_alreadyStarted
|| !audioData
|| audioDataLength
== 0)
254 // Save our audio data in the buffers
255 std::unique_ptr
<CAudioBuffer
> pBuffer(new CAudioBuffer(audioDataLength
));
256 pBuffer
->Set(audioData
, audioDataLength
);
257 m_vecBuffers
.emplace_back(std::move(pBuffer
));
259 if (m_vecBuffers
.size() < m_numBuffers
)
262 std::unique_ptr
<CAudioBuffer
> ptrAudioBuffer
= std::move(m_vecBuffers
.front());
263 m_vecBuffers
.pop_front();
265 // Transfer data to our visualisation
266 m_instance
->AudioData(ptrAudioBuffer
->Get(), ptrAudioBuffer
->Size());
269 void CGUIVisualisationControl::UpdateTrack()
271 if (!m_instance
|| !m_alreadyStarted
)
274 // get the current album art filename
275 m_albumThumb
= CSpecialProtocol::TranslatePath(
276 CServiceBroker::GetGUI()->GetInfoManager().GetImage(MUSICPLAYER_COVER
, WINDOW_INVALID
));
277 if (m_albumThumb
== "DefaultAlbumCover.png")
280 CLog::Log(LOGDEBUG
, "Updating visualization albumart: {}", m_albumThumb
);
282 m_instance
->UpdateAlbumart(m_albumThumb
.c_str());
284 const MUSIC_INFO::CMusicInfoTag
* tag
=
285 CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
289 const std::string
artist(tag
->GetArtistString());
290 const std::string
albumArtist(tag
->GetAlbumArtistString());
291 const std::string
genre(StringUtils::Join(
293 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator
));
295 KODI_ADDON_VISUALIZATION_TRACK track
= {};
296 track
.title
= tag
->GetTitle().c_str();
297 track
.artist
= artist
.c_str();
298 track
.album
= tag
->GetAlbum().c_str();
299 track
.albumArtist
= albumArtist
.c_str();
300 track
.genre
= genre
.c_str();
301 track
.comment
= tag
->GetComment().c_str();
302 track
.lyrics
= tag
->GetLyrics().c_str();
303 track
.trackNumber
= tag
->GetTrackNumber();
304 track
.discNumber
= tag
->GetDiscNumber();
305 track
.duration
= tag
->GetDuration();
306 track
.year
= tag
->GetYear();
307 track
.rating
= tag
->GetUserrating();
309 m_instance
->UpdateTrack(&track
);
312 bool CGUIVisualisationControl::IsLocked()
314 if (m_instance
&& m_alreadyStarted
)
315 return m_instance
->IsLocked();
320 bool CGUIVisualisationControl::HasPresets()
322 if (m_instance
&& m_alreadyStarted
)
323 return m_instance
->HasPresets();
328 int CGUIVisualisationControl::GetActivePreset()
330 if (m_instance
&& m_alreadyStarted
)
331 return m_instance
->GetActivePreset();
336 void CGUIVisualisationControl::SetPreset(int idx
)
338 if (m_instance
&& m_alreadyStarted
)
339 m_instance
->LoadPreset(idx
);
342 std::string
CGUIVisualisationControl::GetActivePresetName()
344 if (m_instance
&& m_alreadyStarted
)
345 return m_instance
->GetActivePresetName();
350 bool CGUIVisualisationControl::GetPresetList(std::vector
<std::string
>& vecpresets
)
352 if (m_instance
&& m_alreadyStarted
)
353 return m_instance
->GetPresetList(vecpresets
);
358 bool CGUIVisualisationControl::InitVisualization()
360 IAE
* ae
= CServiceBroker::GetActiveAE();
361 CWinSystemBase
* const winSystem
= CServiceBroker::GetWinSystem();
362 if (!ae
|| !winSystem
)
365 const std::string addon
= CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
366 CSettings::SETTING_MUSICPLAYER_VISUALISATION
);
367 const ADDON::AddonInfoPtr addonBase
=
368 CServiceBroker::GetAddonMgr().GetAddonInfo(addon
, ADDON::AddonType::VISUALIZATION
);
372 ae
->RegisterAudioCallback(this);
374 auto& context
= winSystem
->GetGfxContext();
376 context
.CaptureStateBlock();
378 float x
= context
.ScaleFinalXCoord(GetXPosition(), GetYPosition());
379 float y
= context
.ScaleFinalYCoord(GetXPosition(), GetYPosition());
380 float w
= context
.ScaleFinalXCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - x
;
381 float h
= context
.ScaleFinalYCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - y
;
386 if (x
+ w
> context
.GetWidth())
387 w
= context
.GetWidth() - x
;
388 if (y
+ h
> context
.GetHeight())
389 h
= context
.GetHeight() - y
;
391 m_instance
= std::make_unique
<KODI::ADDONS::CVisualization
>(addonBase
, x
, y
, w
, h
);
394 m_alreadyStarted
= false;
395 context
.ApplyStateBlock();
399 void CGUIVisualisationControl::DeInitVisualization()
401 if (!m_attemptedLoad
)
404 CWinSystemBase
* const winSystem
= CServiceBroker::GetWinSystem();
408 IAE
* ae
= CServiceBroker::GetActiveAE();
410 ae
->UnregisterAudioCallback(this);
412 m_attemptedLoad
= false;
414 CGUIMessage
msg(GUI_MSG_VISUALISATION_UNLOADING
, m_controlID
, 0);
415 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
417 CLog::Log(LOGDEBUG
, "FreeVisualisation() started");
421 if (m_alreadyStarted
)
423 auto& context
= winSystem
->GetGfxContext();
425 context
.CaptureStateBlock();
427 context
.ApplyStateBlock();
428 m_alreadyStarted
= false;
437 void CGUIVisualisationControl::CreateBuffers()
443 m_numBuffers
+= m_instance
->GetSyncDelay();
444 if (m_numBuffers
> MAX_AUDIO_BUFFERS
)
445 m_numBuffers
= MAX_AUDIO_BUFFERS
;
446 if (m_numBuffers
< 1)
450 void CGUIVisualisationControl::ClearBuffers()
453 m_vecBuffers
.clear();