-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Livemetadata PR #1675
[WIP] Livemetadata PR #1675
Conversation
src/track/trackplaytimers.cpp
Outdated
#include "track/trackplaytimers.h" | ||
|
||
TrackTimers::TimerQt::TimerQt() | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you wrap the QT timer here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because QTimer isn't a subclass of my TrackTimer. And I need TrackTimer to mock timers, otherwise I would have to actually wait until a track is played halfway to check if the signals are correctly emitted.
src/track/track.cpp
Outdated
@@ -70,7 +71,11 @@ Track::Track( | |||
m_record(trackId), | |||
m_bDirty(false), | |||
m_bMarkedForMetadataExport(false), | |||
m_analyzerProgress(-1) { | |||
m_analyzerProgress(-1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that it is appropriate to store transient state of an application component directly inside the Track object. For example m_analyzerProgress does not belong in here and will be removed soon (#1624).
You should keep track of the tracks that are currently played in your component and manage this information there. Connect to the PlayerManager or individual decks for getting notified about which tracks you need to observe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get what you mean exactly? All info I've put in track is the number of ms it has been played along with some timers to update this number and whether if the track is scrobbable or not. Doesn't this information belong to the track? Should I keep a list of the tracks being currently played and update their played time in the player manager?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Track object is already huge and becomes unmanageable if every aspect is thrown in there. It should contain only persistent state that needs to be synchronized with the database and the corresponding file, but not arbitrary transient application state.
Please try to keep the state that is needed for live metadata broadcasting private and local within the corresponding component or module. Use signals/slots for synchronization instead of distributing implementation details into public, shared global state.
Think in terms of modular microservices, even in the context of a desktop application. Consume signals/notifications from other components as input and produce output by emitting signals. All transient state should be isolated within the corresponding module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okey, I get it now. I will move my logic away from Track.
src/track/trackplaytimers.cpp
Outdated
TrackTimers::TimerQt::TimerQt() | ||
{ | ||
connect(&m_Timer,SIGNAL(timeout()), | ||
this,SIGNAL(timeout())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Qt tries hard to emit the timeout() signal as precise as possible.
Unfortunately this interferes with the waveforms, also living in the main thread, which really need this exactness.
As workaround, we have a less exact GUI Tick timer that fires all 50 ms.
It is [Master]", "guiTick50ms"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is why I used an elapsed timer with less granularity. I will use this one then.
It is hard to check the code, without knowing what it should do. Some words or drawings about the data-flow and the related classes would also be nice. This should help to avoid later round-trips, because of some design issues. |
Alright, I'm gonna draw some diagrams. |
src/broadcast/scrobblingmanager.cpp
Outdated
m_CPXFaderCurve(ConfigKey(EngineXfader::kXfaderConfigKey, | ||
"xFaderCurve"),this), | ||
|
||
m_CPXFaderCalibration(ConfigKey(EngineXfader::kXfaderConfigKey, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't you call the function that calculates the xfader gain instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I need the correct parameters which are given by those Control objects, aren't they? I copied the code from EngineMaster.
src/broadcast/scrobblingmanager.cpp
Outdated
} | ||
QMutexLocker locker(&m_mutex); | ||
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin(); | ||
while(it != m_trackList.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use here to the new
C++11 range based for loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I forgot, thanks.
src/broadcast/scrobblingmanager.cpp
Outdated
QMutexLocker locker(&m_mutex); | ||
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin(); | ||
while(it != m_trackList.end()) { | ||
if ((*it)->m_pTrack == pPausedTrack and (*it)->m_pPlayer == sourceDeck) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use && instead of and
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Aren't and, not and or more explicative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably yes, but we just don't use it. I am in doubt that many C++ developers even know that "and" exists.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...including me ;) Please don't use those operator synonyms. I've never seen anyone using them before. They seem to be supported for backward compatibility with C ISO/IEC 646 where they are defined as macros in a separate header file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okey guys, your style, your rules. But I have to say, every programming course I took first year in C++ told me to use "and", "or" and "not" because they are more expressive. And I have to agree ;)
src/broadcast/scrobblingmanager.cpp
Outdated
QMutexLocker locker(&m_mutex); | ||
QLinkedList<TrackInfo*>::Iterator it = m_trackList.begin(); | ||
while(it != m_trackList.end()) { | ||
if ((*it)->m_pTrack == pPausedTrack and (*it)->m_pPlayer == sourceDeck) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the deck important here?
Please add source code comments that explains this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because a track can be playing on a deck and be paused while the same track plays in another deck and not be paused. It's important that both the deck and the track match. I will add comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if a track plays in two decks, with significant volume, the timer counts twice as fast?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized there's several things wrong with this code. First, it uses pausePlayedTime in slotTrackResumed, which is clearly wrong. Two, a same track should have only one timer. Otherwise, if you have the same track in two decks and keep switching the cross fader back and forth the signal will be sent much later. Thanks for pointing it out.
src/broadcast/scrobblingmanager.cpp
Outdated
break; | ||
} | ||
} | ||
if (allPaused) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this one the line, that is related to the deadlock?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
src/track/tracktiminginfo.cpp
Outdated
if (m_pElapsedTimer->isValid()) { | ||
m_playedMs += m_pElapsedTimer->elapsed(); | ||
m_pElapsedTimer->invalidate(); | ||
QObject::disconnect(m_pTimer.get(),SIGNAL(timeout()), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disconnect locks a muted internally. You should not connect and disconnect signals after initialize.
Do you use Qt5? I am suffering from bufferunderflows. Maybe this is related.
Does it still deadlock if you remove this line?
Is there a way around using a timer signal here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, there's a deadlock even if I remove every single connect and disconnect call inside that class. I tried using valgrind but it's giving me an illegal instruction error. I'm going to investigate further. Also, I don't use Qt5, I will check on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Qt5 will introduce new problems. I would stick with Qt4 in this case.
I will have a look into this soon today.
src/broadcast/scrobblingmanager.cpp
Outdated
} | ||
} | ||
if (allPaused) | ||
pausedTrackInfo->m_trackInfo.pausePlayedTime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You crash is not a deadlock, it is a Segmentation fault, which happens when you access memory, not allocated by the process. In this case, you access memory via the uninitialized pointer pausedTrackInfo.
It is a good idea to always initialize empty pointers with "nullptr". Than you can decorate your code with
DEBUG_ASSERT(pausedTrackInfo) in various places to detect such design issues early.
If you start mixxx with --debugAssertBreak under GDB, it will hit a breakpoint, instead of just abort.
Very handy :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, how can a seg fault keep all threads in a futex? Wouldn't it kill the process? Thanks for the help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually kills the process here on my Ubuntu Trusty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mybe we have an additional deadlock?
src/broadcast/scrobblingmanager.cpp
Outdated
pausedTrackInfo = trackInfo; | ||
for (BaseTrackPlayer *player : trackInfo->m_players) { | ||
BaseTrackPlayerImpl *playerImpl = | ||
qobject_cast<BaseTrackPlayerImpl*>(player); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid upcasting. here it is better to put the required functions as pure virtuals into the base class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to pollute the interface with functions I need in case someone complains. But I will if you're ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readability of the whole code base finally counts.
src/broadcast/scrobblingmanager.cpp
Outdated
void ScrobblingManager::slotTrackResumed(TrackPointer pResumedTrack) { | ||
//Extra functional decision to only track main decks. | ||
Deck *sourceDeck = qobject_cast<Deck*>(sender()); | ||
if (sourceDeck == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!sourceDeck) { or == nullptr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw the specification of qobject_cast and it returns 0, I read the standard and nullptr doesn't have to be 0, same with NULL that is implementation defined. So I went with 0 because that's what the Qt doc says. Though I agree it's more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They mean the actual register value. This is relevant if you reinterpret a nullptr as integer. It is not guarantied that this is 0. It is more about type safety here.
0 is an integer literal, that is casted implicit to the nullptr type. Using !.. or nullptr avoids this implicit cast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, ok, then I will use !
src/broadcast/scrobblingmanager.cpp
Outdated
|
||
void ScrobblingManager::slotTrackResumed(TrackPointer pResumedTrack) { | ||
//Extra functional decision to only track main decks. | ||
Deck *sourceDeck = qobject_cast<Deck*>(sender()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid these kind of implication. It is better to maintain to send the desire informations explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okey.
src/broadcast/scrobblingmanager.cpp
Outdated
bool trackAlreadyAdded = false; | ||
for (TrackInfo *trackInfo : m_trackList) { | ||
if (trackInfo->m_pTrack == pNewTrack) { | ||
trackInfo->m_players.append(newDeck); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing a pointer you not own is always kind of dangerous, because you don't know the live time of it.
It is better to use a shared pointer, or the group string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the player manager doesn't destroy the decks until it itself gets destroyed, and this object gets destroyed too. But I will use a group string because shared pointer implies modifying player manager.
src/broadcast/scrobblingmanager.cpp
Outdated
} | ||
} | ||
if (!trackAlreadyAdded) { | ||
TrackInfo *newTrackInfo = new TrackInfo(pNewTrack,newDeck); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can consider to make it a unique pointer.
To make it work, m_trackList has to be a std:: container.
src/broadcast/scrobblingmanager.cpp
Outdated
break; | ||
QLinkedList<BaseTrackPlayer*>::iterator it = | ||
trackInfo->m_players.begin(); | ||
while (it != trackInfo->m_players.end()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this an endless loop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing it out
class BaseTrackPlayer; | ||
class PlayerManager; | ||
|
||
class ScrobblingManager : public QObject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the name of this class is not self explaining.
How many "Current tracks" we will have finally?
- The current scrobble track
- The current Track for Metadata Streaming (RDS)
- The current Track for history playlist
- The current Track driving the master clock for beat matching (@kshitij98 project)
Will this become a CurrentTrackManager or CurrentTrackCalculator?
@kshitij98: could you reuse portions of this code? Or could you share your rules for a master clock track?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current scrobble track, metadata streaming and history playlist are all the same, the one given by PlayerInfo.h. This manager takes into account when a track is loaded, paused, etc. and checks if they're audible every second to update the timers, and when they are it sends a scrobble signal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kshitij98: could you reuse portions of this code? Or could you share your rules for a master clock track?
PlayerInfo::getCurrentPlayingTrack
returns the most audible track which should drive the master clock. This is not a part of my project so I will be able to continue working on it only once I'm done with my project, or I have sufficient time in between.
There is some work left to do. |
https://github.com/daschuer/mixxx/tree/Livemetadata |
in the sense that i technically know C++, yeah, but haven't done anything with QT in ages and never even taken a look at the mixxx codebase, so not sure if i'd be able to offer much help, i'd probably be more of a hindrance :P |
One of the main reason Mixxx is so vivit, is the underestimated |
good point, but given i know nothing about the codebase, very little about the used frameworks, and have never done whatever an "independent code review" entails i'm afraid i'm currently the wrong one for that task :/ honestly i'd probably do a better job trying to implement a new feature in a foreign codebase than to review someone else's code |
I'll try to continue this once I'm done with my master's. Right now I'm starting my Master's thesis and working at the same time, so it's gonna be quite hard for me to do extra work on the side. |
Would love this feature! Hope it get's finished/merged soon |
@ligi are you volunteering to finish it? |
This PR is marked as stale because it has been open 90 days with no activity. |
I will pick this one up and get it merged |
Great, thank you very much! What is you approach? |
I just need to remote control mix a little bit. When leaving the room I want to pause it from home assitant and use my kdeconnect controls on the mobile etc. |
That sound good. Here are many conflicts. Do you plan to solve them or to start over? |
I recommend to start over and only borrow what is needed, be it ideas or code. |
But isn't D-Bus is only available on Linux? We should use cross-platform solutions for application features if ever possible. |
Yes it is Linux only. But it is THE interface to add info to the system tray. There is no other way than using platform dependent solutions for this. |
If it is only supposed to be used for the system tray ok. But if it is intended as a general purpose IPC interface than not. |
I guess we can close this in favor of #3483. If not, please reopen. |
This is only intended for code review atm, not to be merged.