summaryrefslogtreecommitdiffstats
path: root/xbmc/guilib/GUIVisualisationControl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/guilib/GUIVisualisationControl.cpp')
-rw-r--r--xbmc/guilib/GUIVisualisationControl.cpp452
1 files changed, 452 insertions, 0 deletions
diff --git a/xbmc/guilib/GUIVisualisationControl.cpp b/xbmc/guilib/GUIVisualisationControl.cpp
new file mode 100644
index 0000000..0399edd
--- /dev/null
+++ b/xbmc/guilib/GUIVisualisationControl.cpp
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIVisualisationControl.h"
+
+#include "GUIComponent.h"
+#include "GUIInfoManager.h"
+#include "GUIUserMessages.h"
+#include "GUIWindowManager.h"
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/Visualization.h"
+#include "addons/addoninfo/AddonType.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "cores/AudioEngine/Interfaces/AE.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/actions/Action.h"
+#include "input/actions/ActionIDs.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+namespace
+{
+constexpr unsigned int MAX_AUDIO_BUFFERS = 16;
+} // namespace
+
+CAudioBuffer::CAudioBuffer(int iSize)
+{
+ m_iLen = iSize;
+ m_pBuffer = new float[iSize];
+}
+
+CAudioBuffer::~CAudioBuffer()
+{
+ delete[] m_pBuffer;
+}
+
+const float* CAudioBuffer::Get() const
+{
+ return m_pBuffer;
+}
+
+int CAudioBuffer::Size() const
+{
+ return m_iLen;
+}
+
+void CAudioBuffer::Set(const float* psBuffer, int iSize)
+{
+ if (iSize < 0)
+ return;
+
+ memcpy(m_pBuffer, psBuffer, iSize * sizeof(float));
+ for (int i = iSize; i < m_iLen; ++i)
+ m_pBuffer[i] = 0;
+}
+
+CGUIVisualisationControl::CGUIVisualisationControl(
+ int parentID, int controlID, float posX, float posY, float width, float height)
+ : CGUIControl(parentID, controlID, posX, posY, width, height)
+{
+ ControlType = GUICONTROL_VISUALISATION;
+}
+
+CGUIVisualisationControl::CGUIVisualisationControl(const CGUIVisualisationControl& from)
+ : CGUIControl(from)
+{
+ ControlType = GUICONTROL_VISUALISATION;
+}
+
+std::string CGUIVisualisationControl::Name()
+{
+ if (m_instance == nullptr)
+ return "";
+ return m_instance->Name();
+}
+
+bool CGUIVisualisationControl::OnMessage(CGUIMessage& message)
+{
+ if (m_alreadyStarted)
+ {
+ switch (message.GetMessage())
+ {
+ case GUI_MSG_GET_VISUALISATION:
+ message.SetPointer(this);
+ return true;
+ case GUI_MSG_VISUALISATION_RELOAD:
+ FreeResources(true);
+ return true;
+ case GUI_MSG_PLAYBACK_STARTED:
+ m_updateTrack = true;
+ return true;
+ default:
+ break;
+ }
+ }
+ return CGUIControl::OnMessage(message);
+}
+
+bool CGUIVisualisationControl::OnAction(const CAction& action)
+{
+ if (m_alreadyStarted)
+ {
+ switch (action.GetID())
+ {
+ case ACTION_VIS_PRESET_NEXT:
+ m_instance->NextPreset();
+ break;
+ case ACTION_VIS_PRESET_PREV:
+ m_instance->PrevPreset();
+ break;
+ case ACTION_VIS_PRESET_RANDOM:
+ m_instance->RandomPreset();
+ break;
+ case ACTION_VIS_RATE_PRESET_PLUS:
+ m_instance->RatePreset(true);
+ break;
+ case ACTION_VIS_RATE_PRESET_MINUS:
+ m_instance->RatePreset(false);
+ break;
+ case ACTION_VIS_PRESET_LOCK:
+ m_instance->LockPreset();
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ return CGUIControl::OnAction(action);
+}
+
+void CGUIVisualisationControl::Process(unsigned int currentTime, CDirtyRegionList& dirtyregions)
+{
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingAudio())
+ {
+ if (m_bInvalidated)
+ FreeResources(true);
+
+ if (!m_instance && !m_attemptedLoad)
+ {
+ InitVisualization();
+
+ m_attemptedLoad = true;
+ }
+ else if (m_callStart && m_instance)
+ {
+ auto& context = CServiceBroker::GetWinSystem()->GetGfxContext();
+
+ context.CaptureStateBlock();
+ if (m_alreadyStarted)
+ {
+ m_instance->Stop();
+ m_alreadyStarted = false;
+ }
+
+ std::string songTitle = URIUtils::GetFileName(g_application.CurrentFile());
+ const MUSIC_INFO::CMusicInfoTag* tag =
+ CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
+ if (tag && !tag->GetTitle().empty())
+ songTitle = tag->GetTitle();
+ m_alreadyStarted = m_instance->Start(m_channels, m_samplesPerSec, m_bitsPerSample, songTitle);
+ context.ApplyStateBlock();
+ m_callStart = false;
+ m_updateTrack = true;
+ }
+ else if (m_updateTrack)
+ {
+ /* Initial update of currently processed track */
+ UpdateTrack();
+ m_updateTrack = false;
+ }
+
+ if (m_instance && m_instance->IsDirty())
+ MarkDirtyRegion();
+ }
+
+ CGUIControl::Process(currentTime, dirtyregions);
+}
+
+void CGUIVisualisationControl::Render()
+{
+ if (m_instance && m_alreadyStarted)
+ {
+ auto& context = CServiceBroker::GetWinSystem()->GetGfxContext();
+
+ /*
+ * set the viewport - note: We currently don't have any control over how
+ * the addon renders, so the best we can do is attempt to define
+ * a viewport??
+ */
+ context.SetViewPort(m_posX, m_posY, m_width, m_height);
+ context.CaptureStateBlock();
+ m_instance->Render();
+ context.ApplyStateBlock();
+ context.RestoreViewPort();
+ }
+
+ CGUIControl::Render();
+}
+
+void CGUIVisualisationControl::UpdateVisibility(const CGUIListItem* item /* = nullptr*/)
+{
+ // if made invisible, start timer, only free addonptr after
+ // some period, configurable by window class
+ CGUIControl::UpdateVisibility(item);
+ if (!IsVisible() && m_attemptedLoad)
+ FreeResources();
+}
+
+bool CGUIVisualisationControl::CanFocusFromPoint(const CPoint& point) const
+{ // mouse is allowed to focus this control, but it doesn't actually receive focus
+ return IsVisible() && HitTest(point);
+}
+
+void CGUIVisualisationControl::FreeResources(bool immediately)
+{
+ DeInitVisualization();
+
+ CGUIControl::FreeResources(immediately);
+
+ CLog::Log(LOGDEBUG, "FreeVisualisation() done");
+}
+
+void CGUIVisualisationControl::OnInitialize(int channels, int samplesPerSec, int bitsPerSample)
+{
+ m_channels = channels;
+ m_samplesPerSec = samplesPerSec;
+ m_bitsPerSample = bitsPerSample;
+ m_callStart = true;
+}
+
+void CGUIVisualisationControl::OnAudioData(const float* audioData, unsigned int audioDataLength)
+{
+ if (!m_instance || !m_alreadyStarted || !audioData || audioDataLength == 0)
+ return;
+
+ // Save our audio data in the buffers
+ std::unique_ptr<CAudioBuffer> pBuffer(new CAudioBuffer(audioDataLength));
+ pBuffer->Set(audioData, audioDataLength);
+ m_vecBuffers.emplace_back(std::move(pBuffer));
+
+ if (m_vecBuffers.size() < m_numBuffers)
+ return;
+
+ std::unique_ptr<CAudioBuffer> ptrAudioBuffer = std::move(m_vecBuffers.front());
+ m_vecBuffers.pop_front();
+
+ // Transfer data to our visualisation
+ m_instance->AudioData(ptrAudioBuffer->Get(), ptrAudioBuffer->Size());
+}
+
+void CGUIVisualisationControl::UpdateTrack()
+{
+ if (!m_instance || !m_alreadyStarted)
+ return;
+
+ // get the current album art filename
+ m_albumThumb = CSpecialProtocol::TranslatePath(
+ CServiceBroker::GetGUI()->GetInfoManager().GetImage(MUSICPLAYER_COVER, WINDOW_INVALID));
+ if (m_albumThumb == "DefaultAlbumCover.png")
+ m_albumThumb = "";
+ else
+ CLog::Log(LOGDEBUG, "Updating visualization albumart: {}", m_albumThumb);
+
+ m_instance->UpdateAlbumart(m_albumThumb.c_str());
+
+ const MUSIC_INFO::CMusicInfoTag* tag =
+ CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag();
+ if (!tag)
+ return;
+
+ const std::string artist(tag->GetArtistString());
+ const std::string albumArtist(tag->GetAlbumArtistString());
+ const std::string genre(StringUtils::Join(
+ tag->GetGenre(),
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator));
+
+ KODI_ADDON_VISUALIZATION_TRACK track = {};
+ track.title = tag->GetTitle().c_str();
+ track.artist = artist.c_str();
+ track.album = tag->GetAlbum().c_str();
+ track.albumArtist = albumArtist.c_str();
+ track.genre = genre.c_str();
+ track.comment = tag->GetComment().c_str();
+ track.lyrics = tag->GetLyrics().c_str();
+ track.trackNumber = tag->GetTrackNumber();
+ track.discNumber = tag->GetDiscNumber();
+ track.duration = tag->GetDuration();
+ track.year = tag->GetYear();
+ track.rating = tag->GetUserrating();
+
+ m_instance->UpdateTrack(&track);
+}
+
+bool CGUIVisualisationControl::IsLocked()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->IsLocked();
+
+ return false;
+}
+
+bool CGUIVisualisationControl::HasPresets()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->HasPresets();
+
+ return false;
+}
+
+int CGUIVisualisationControl::GetActivePreset()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetActivePreset();
+
+ return -1;
+}
+
+void CGUIVisualisationControl::SetPreset(int idx)
+{
+ if (m_instance && m_alreadyStarted)
+ m_instance->LoadPreset(idx);
+}
+
+std::string CGUIVisualisationControl::GetActivePresetName()
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetActivePresetName();
+
+ return "";
+}
+
+bool CGUIVisualisationControl::GetPresetList(std::vector<std::string>& vecpresets)
+{
+ if (m_instance && m_alreadyStarted)
+ return m_instance->GetPresetList(vecpresets);
+
+ return false;
+}
+
+bool CGUIVisualisationControl::InitVisualization()
+{
+ IAE* ae = CServiceBroker::GetActiveAE();
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!ae || !winSystem)
+ return false;
+
+ const std::string addon = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_MUSICPLAYER_VISUALISATION);
+ const ADDON::AddonInfoPtr addonBase =
+ CServiceBroker::GetAddonMgr().GetAddonInfo(addon, ADDON::AddonType::VISUALIZATION);
+ if (!addonBase)
+ return false;
+
+ ae->RegisterAudioCallback(this);
+
+ auto& context = winSystem->GetGfxContext();
+
+ context.CaptureStateBlock();
+
+ float x = context.ScaleFinalXCoord(GetXPosition(), GetYPosition());
+ float y = context.ScaleFinalYCoord(GetXPosition(), GetYPosition());
+ float w = context.ScaleFinalXCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - x;
+ float h = context.ScaleFinalYCoord(GetXPosition() + GetWidth(), GetYPosition() + GetHeight()) - y;
+ if (x < 0)
+ x = 0;
+ if (y < 0)
+ y = 0;
+ if (x + w > context.GetWidth())
+ w = context.GetWidth() - x;
+ if (y + h > context.GetHeight())
+ h = context.GetHeight() - y;
+
+ m_instance.reset(new KODI::ADDONS::CVisualization(addonBase, x, y, w, h));
+ CreateBuffers();
+
+ m_alreadyStarted = false;
+ context.ApplyStateBlock();
+ return true;
+}
+
+void CGUIVisualisationControl::DeInitVisualization()
+{
+ if (!m_attemptedLoad)
+ return;
+
+ CWinSystemBase* const winSystem = CServiceBroker::GetWinSystem();
+ if (!winSystem)
+ return;
+
+ IAE* ae = CServiceBroker::GetActiveAE();
+ if (ae)
+ ae->UnregisterAudioCallback(this);
+
+ m_attemptedLoad = false;
+
+ CGUIMessage msg(GUI_MSG_VISUALISATION_UNLOADING, m_controlID, 0);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+
+ CLog::Log(LOGDEBUG, "FreeVisualisation() started");
+
+ if (m_instance)
+ {
+ if (m_alreadyStarted)
+ {
+ auto& context = winSystem->GetGfxContext();
+
+ context.CaptureStateBlock();
+ m_instance->Stop();
+ context.ApplyStateBlock();
+ m_alreadyStarted = false;
+ }
+
+ m_instance.reset();
+ }
+
+ ClearBuffers();
+}
+
+void CGUIVisualisationControl::CreateBuffers()
+{
+ ClearBuffers();
+
+ m_numBuffers = 1;
+ if (m_instance)
+ m_numBuffers += m_instance->GetSyncDelay();
+ if (m_numBuffers > MAX_AUDIO_BUFFERS)
+ m_numBuffers = MAX_AUDIO_BUFFERS;
+ if (m_numBuffers < 1)
+ m_numBuffers = 1;
+}
+
+void CGUIVisualisationControl::ClearBuffers()
+{
+ m_numBuffers = 0;
+ m_vecBuffers.clear();
+}