Create JavascriptPlayerProxy#14314
Conversation
9dc994c to
ca4c97c
Compare
|
FYI the corresponding Zulip convo is |
Thanks, I missed that! Still getting used to how the communication is spread across the different channels. |
Thanks for chirping in @cr7pt0gr4ph7. I appreciate your suggestion but IMO it has also been discussed previously and rejected (See the corresponding zulip thread). The |
I wasn't aware of the Zulip discussion when I wrote that comment. I've hidden my comment and added a note at the top to avoid future confusion, sorry for the noise. I'll continue the discussion w.r.t. API design over at Zulip. |
|
Additionally, I'd also like to make the proposal of preactively linking Zulip discussions in GitHub issues and PRs for future cross-reference when these contain highly relevant discussion, such as here ;) |
acolombier
left a comment
There was a problem hiding this comment.
This is looking pretty much good to go IMO!
Only thing is that it would be worth to combining our effort with #14516, which break the dependencies between the player and a track (quick TL;DR on why this is needed - QML needs a way to interact with track that aren't loaded on player). Note that this change also removes all the waveform left over bits, that are now irrelevant thanks to the rendergraph waveform library, and this should help simplifying your PR.
Also, it would be great if you could put typing instructions in res/controllers/engine-api.d.ts and also provide a small example on how this is intended to be used. Also, feel free to implement a basic usage in a controller mapping you feel relevant just to help with understanding and testing (just make sure to use a separate commit for it for better change control)
|
@acolombier Since you're moving most of |
|
The Deck/Player might be better parent object here than the track, as it could provide additional information that depend on playposition or rate, like the formated actual key string, which is often used in jogwheel displays. |
|
Ok so we're back to defining a wrapper around |
The idea with the split is to allow both. Player (which can be a deck, sampler or preview) will have playing information (rate, position, ...) as well current loaded track, which itself will have its own information, unrelated to player. |
This code is fine, it just needs to be adjusted as described by @acolombier above! |
|
Well @acolombier's PR splits the code in two classes so I don't really understand how this code fits. |
ca4c97c to
b7ee0c3
Compare
|
I rebased this PR and added the implementation. |
| // Don't set a parent here, so that the QML engine deletes the object when | ||
| // the corresponding JS object is garbage collected. | ||
| JavascriptPlayerProxy* pPlayerProxy = new JavascriptPlayerProxy( | ||
| s_pPlayerManager->getPlayer(deck), nullptr); |
There was a problem hiding this comment.
PlayerManager::getPlayer will return a null ptr if deck doesn't exist, could you please handle that gracefully and return a QJSEngine::newErrorObject?
There was a problem hiding this comment.
fyi, this a new pattern where our API would return an error. Usually it just returns undefined (which would be the case with a nullptr). Since we don't really return different errors at the moment, I would prefer to stick with the consistency of undefined. Wdyt?
There was a problem hiding this comment.
Happy with the middle ground of returning null if we think this is best. I think we shouldn't return an invalid JavascriptPlayerProxy tho
There was a problem hiding this comment.
Yes I changed that in the last commit.
There was a problem hiding this comment.
Can I resolve this so it's clearer for me what's left?
| Q_PROPERTY(bool isLoaded READ isLoaded NOTIFY trackChanged) | ||
| Q_PROPERTY(QString artist READ getArtist NOTIFY artistChanged) | ||
| Q_PROPERTY(QString title READ getTitle NOTIFY titleChanged) | ||
| Q_PROPERTY(QString album READ getAlbum NOTIFY albumChanged) | ||
| Q_PROPERTY(QString albumArtist READ getAlbumArtist NOTIFY albumArtistChanged) | ||
| Q_PROPERTY(QString genre READ getGenre STORED false NOTIFY genreChanged) | ||
| Q_PROPERTY(QString composer READ getComposer NOTIFY composerChanged) | ||
| Q_PROPERTY(QString grouping READ getGrouping NOTIFY groupingChanged) | ||
| Q_PROPERTY(QString year READ getYear NOTIFY yearChanged) | ||
| Q_PROPERTY(QString trackNumber READ getTrackNumber NOTIFY trackNumberChanged) | ||
| Q_PROPERTY(QString trackTotal READ getTrackTotal NOTIFY trackTotalChanged) |
There was a problem hiding this comment.
I'm wondering if it is necessary to implement NOTIFY here. Since this code won't be used by QML (as it has its own implementation), there is no need to provide this to support property binding. This will allow you to remove most of the signals and reduce the code duplication with src/qml/qmltrackproxy.h
There was a problem hiding this comment.
You mean for this property or all of them? I think NOTIFY is useful. In combination with #13935, it allows the following:
engine.getPlayer("deck1").titleChanged.connect(newTitle => {
displayTitle(
engine.convertCharset(engine.WellKnownCharsets.Latin1, newTitle)
)
}) I could even add a convenience function to ComponentsJS
There was a problem hiding this comment.
Yup, we should def also use signals from JS. Otherwise we'll need yet another makeConnection like facility or people will abuse (likely only tangentially related) COs instead.
There was a problem hiding this comment.
Lets split any ComponentsJS changes to another PR though
There was a problem hiding this comment.
Ah sorry, I didn't see that this will actively be supported in the script. This is looking good then. The duplication remains a bit of a bummer IMO, but don't want to block this further so if this isn't a problem for other member, happy to ignore!
There was a problem hiding this comment.
Can I resolve this so it's clearer for me what's left?
cabd6b9 to
217ecee
Compare
| // Don't set a parent here, so that the QML engine deletes the object when | ||
| // the corresponding JS object is garbage collected. | ||
| JavascriptPlayerProxy* pPlayerProxy = new JavascriptPlayerProxy( | ||
| s_pPlayerManager->getPlayer(deck), nullptr); |
There was a problem hiding this comment.
fyi, this a new pattern where our API would return an error. Usually it just returns undefined (which would be the case with a nullptr). Since we don't really return different errors at the moment, I would prefer to stick with the consistency of undefined. Wdyt?
e750320 to
d960c0a
Compare
|
TypeScript declaration is LGTM now too! Thank you! |
|
friendly ping @christophehenry |
|
@Swiftb0y Did you mean to ping @JoergAtGithub? I'm waiting for their input on how to load a track to the player and test the slots. |
|
Ah, I see. I didn't know where we were. Then this is indeed a friendly reminder @JoergAtGithub |
|
Ah I see @JoergAtGithub opened christophehenry#3. I don't why I didn't get notified… I may be able to progress this week. |
…pping_validation_test and controllerscriptenginelegacy_test executable without crash
|
Alright! Tests done! Edit: ah… I should add a test for the |
| for (auto [key, value] : expectedValues.asKeyValueRange()) { | ||
| EXPECT_QSTRING_EQ(value, qjsvalue_cast<QString>(evaluate(QString("player.%1").arg(key)))); | ||
| EXPECT_QSTRING_EQ(value, qjsvalue_cast<QString>(evaluate(QString("result.%1").arg(key)))); | ||
| } |
There was a problem hiding this comment.
Is there a GoogleTest equivalent to Python's unittest.TestCase.subTest? Couldn't find one.
There was a problem hiding this comment.
Not really. The decoding test has some code that tries to accomplish something similar, but I don't want you to reuse that. Looking at your test code however, I think you'll want to write a Value-Parameterized Test.
There was a problem hiding this comment.
I think I'll just go with a custom failure message.
There was a problem hiding this comment.
thats fine too, though a parametrized test wouldn't really be that much complicated IMO.
fbd30fb to
c6f0e2d
Compare
| while (deck->getEngineDeck()->getEngineBuffer()->isTrackLoaded()) { | ||
| QTest::qSleep(100); | ||
| } |
There was a problem hiding this comment.
Sadly, this doesn't work. The test just hangs. If I execute this in the debugger with breakpoints, deck->slotEjectTrack(1.0) correctly ejects the track and the test passes. So I'm not correctly waiting here. There's a concurrency problem. I'd accept any help on how to solve this.
There was a problem hiding this comment.
Did you try to eject from JavaScript using engine.setValue('[Channel1]', 'eject', 1); ?
There was a problem hiding this comment.
I admit I didn't have that idea. Lemme try.
Edit: @JoergAtGithub hmm… This solution seems to not even eject the track. Even in the debugger, the event aren't fired :thinking:
There was a problem hiding this comment.
I would be fine if the testcase doens't check the eject case.
There was a problem hiding this comment.
Alright, deal.
There was a problem hiding this comment.
maybe also processEvents() in the busy-wait loop?
There was a problem hiding this comment.
Sadly no, that still hangs or not even eject, depending on the solution. I tried a number of combinations around this.
There was a problem hiding this comment.
You probably need to call it twice, as in other tests:
mixxx/src/test/controlobjectscripttest.cpp
Lines 85 to 91 in 14c19a5
There was a problem hiding this comment.
I called the processEvents of the test class which already does that. But it doesn't make a difference.
88fa23b to
9f525d5
Compare
9f525d5 to
80e959a
Compare
JoergAtGithub
left a comment
There was a problem hiding this comment.
LGTM! Thank you for your contribution and your patience!
|
After merging this Mixxx crashed during shutdown: See #14982 |
|
Congratulations for the work and perseverance. |
|
Awesome! It's the last piece I needed to propose an official DN-S3700 controller ❤️ |
Continuation of #14175.