Dev Diary 7: Koto April Progress Report (B-side)
dev-diary koto opensource tech

In this development diary, I provide an update on progress made on Koto in the second half of April 2021!

This content releases early on my Patreon! If you like what you see and want to support development of this and many other projects, check it out. If Patreon isn’t your thing, I am on Liberapay as well!

Playlists

In Dev Diary 6, I had the following goals related to playlist functionality:

  1. Implementation of a playlist-metadata signal for KotoPlaylist so we can notify when a playlist has been modified.
  2. Updating our logic for going to the previous and next tracks to leverage our currently selected model (or default based on the type we set during creation) and base the tracks on that instead.
  3. Implement the track rendering in the GtkListView.
  4. Implement the selection handling and GtkActionBar rendering, events, etc. when clicking on one or more tracks.
  5. Implement playlist-removed handling in the KotoNav to delete the button.

I have made significant progress with the playlist functionality to the point I can now create a playlist, add and remove tracks from playlists, dynamically sort playlists, and based on the sort order it will change the playback order. While on its face the functionality I described may seem simple, there was actually some early rewrites of Playlist functionality and changes to our playback engine to accomplish this.

Let us break down why this is more complex:

Our Track list in the Playlist page leverages a GtkListView. As I described in Dev Diary 6, this allows us to provide an efficient listing of tracks by rebinding data to the existing list items as you scroll. This data is stored in the GListStore and we can dynamically add and remove pointers to KotoIndexedTracks on the list model, which updates the list on the Playlist page. However, it has no functionality for getting the previous and next items in a GListStore based on the position of whatever KotoIndexedTrack you provide, so to accomplish that, we have different GLib data structures to keep track of that.

Since we are able to sort our GListStore, this will update the graphical model (like changing the order of the tracks in the list). This is great and is super easy to do with our sorting functionality and the GtkListView. However, that does not actually update the data representation. Say you were playing tracks based on the position they were added, but you decided to mix things up and sort by artist. Great, it is now showing in the graphical list the sorted artists. However since the GListStore has no mechanism to go back and forth, we need a separate GQueue to represent the sorted tracks as well. If you do not update the GQueue of the sorted tracks, suddenly your graphical list is jumping around because it is not playing your tracks in your desired “artist” sorting model. That is kind of a problem.

So to resolve this, we needed to refactor how and when we apply our desired sorting models, the generating of the sorted GQueue, and the respective GListStore + GListModel. Prior to this change, our sorting function was focused on sorting the GQueue then rebuilding a GListStore out of that, which just means you are freeing and allocating memory for effectively the same list, plus or minus a few items, over and over again, and it gets costly if you are adding or removing multiple items to a playlist at once, or re-sorting a GQueue and building a new GListStore every time you want to update the graphical UI and data model based on your sorting model. With the refactored and rewritten sorting, we do the following:

  1. We create two different GQueue objects and one GListStore of KOTO_TYPE_INDEXED_TRACK. One of the GQueue is basically for the unmodified version of tracks, so no matter the sorting it is the same. The other GQueue is our sorted tracks, which is used to determine what the previous and next tracks are based on the current UUID of the current track playing (if any). Our GListStore is used for any models which leverage it via GListModel for a GtkListView, such as the Playlist page.
  2. Whenever we add a track, whether during the initial loading of the Playlist or otherwise, we add the UUID of the track to the tracks and sorted tracks GQueues and append the KotoIndexedTrack to the GListStore.
  3. If we have marked the playlist as finalized (this is done so we can track whether we should do a sort or wait until a mass addition of tracks are added to a playlist, e.g. during first load of Koto), we will re-sort the sorted GQueue and GListStore. This updates our data model and the graphical model instantly!
  4. Our “apply model” for changing the sorting model no longer rebuilds the GQueue and GListStore in their entirety. Instead, we apply a re-sort of the GQueue via koto_playlist_model_sort_by_uuid, which gets the two KotoIndexedTracks by the UUIDs provided in the GQueue and passes it to koto_playlist_model_sort_by_track, which is also used by our GListStore. This allows us to keep the data model and the graphical model in sync.

So the tldr of all of this is: When you change the sorting model via the UI, it updates the data model and your playlist playback next / previous tracks update accordingly. This was a royal pain to get sorted, as was using the GtkListView to begin with, so unfortunately that put me a little behind on other items.

For example, I was not able to get around to implementing the playlist-metadata signal for KotoPlaylist yet. That is trivial and can be done within a couple hours though, so not really worried on that front. I was not able to get around to implementing the functionality for using GtkActionBar for the selection handling either due to the time eaten up by implementing the GtkListView or various GTK4 Popover bugs.

Our GTK4 Popover called KotoAddRemoveTrackPopover, while not currently fancy, does the job for adding and removing one or more tracks from a playlist. It is already developed in a manner that supports passing in a list of tracks, which means we can add or remove multiple tracks at once. This popover leverages our KotoCartographer’s playlist-added and playlist-removed signals to dynamically create GtkCheckButtons for each playlist, and whenever we set the tracks we want to add or remove before showing the popover, we will update the popover checkboxes in a predictable manner, which is:

In the second case for example, if you have 4 out of 5 tracks in a playlist, clicking to check the playlist will add the remaining 1 track to the playlist. Then, all of them are checked, so unchecking will remove all of them.

This will be able to be easily coupled into our planned use of GtkActionBar in the near future. For now I am using it in the album’s view and individual track items.

Other Items

Work over the last couple weeks has almost exclusively been related to Playlist functionality, however I did get around to a few other items I really wanted to take care of:

  1. Implement helper functions as part of our KotoButton for “click” and touch gesture setup to avoid repetitive GtkEventController / Gesture code.
  2. Refactoring the album artwork image used in the KotoAlbumView into a generic widget that can be leveraged in our KotoPlaylistPage.
  3. Finish necessary VS Code bits so I can hopefully launch Koto in various modes and stop using GNOME Builder (too buggy for me).

There was a fair bit of cleanup as a result of the introduction of a new koto_button_add_click_handler function. This will use our predefined GTK gestures and event controllers, allowing us to just pass in a GLib Callback and the desired primary or secondary button enumerated value and it sets up the signal connection for us. This transforms 5-6 repetitive lines used pretty much everywhere into a single line, which is nice.

Our album artwork image has been refactored into a KotoCoverArtButton component, allowing us to reuse it across the playlist page and the KotoAlbumView for playing either a playlist or an album.

I finished my move from GNOME Builder to Visual Studio Code. This was achievable thanks to a mix of their task system and a bit of shoehorning and trickery of their “launch” system as well, which is typically used for debugging purposes but just pass a “miDebuggerPath” of nothing (empty string) for it to not launch the app with gdb.

My uncrustify config is mostly done, aside from some minor issues with pointer positions being inconsistent between variables and return types for functions. Simple enough to fix up, just been focused on actually implementing code.

I did some polishing of the theming. The track information in the playerbar looks halfway decent now and some parts of the UI is using what is likely going to be the primary color of Koto (a lime-ish green). Pretty happy with it so far, but feedback is always appreciated.

Upcoming

Playlist functionality is starting to get wrapped up and I expect it to be done over the next couple weeks. At that point, I can actively dogfood Koto and it brings it one step closer to being available for testing as alpha builds.

Here is what is coming up over the next couple weeks:

  1. Implementation of a playlist-metadata signal for KotoPlaylist so we can notify when a playlist has been modified.
  2. Implement the selection handling and GtkActionBar rendering, events, etc. when clicking on one or more tracks. This will be done in the album view and the playlist page.
  3. Implement playlist modification UX.
  4. Finish the uncrustify config so I can have a standardized code styling.
  5. Implement song-specific playback
  6. Implement notifications for track switching.
  7. Rename “indexed” classes like KotoIndexedArtist to just KotoArtist
  8. Start cleanup of indexer and library code for multiple library “types” in preparation multi-library support leading into audiobooks.

As I mentioned in Dev Diary #6, my goal was to adopt GitHub’s project Kanban workboards and mass file issues on work I need to get done, in addition to various milestones. The intent with this was to provide clarity on progress, when various builds should be expected, and transparency on planned work.

I have filed 30 “issues” for tracking various TODO items and broke them apart across Alpha 1, Alpha 2, and Beta 1. As we get closer to various builds / milestones, items may shift around or be re-prioritized, but this should provide a clear path leading closer to a stable release. There is still a fair bit to do after beta 1, like auto-generated playlists for different times of the day (as an example) but should be comprehensive enough for folks.

Streams

All development streams happen on my Twitch every Tuesday and Thursday from 12pm-5pm GMT+3 / EEST (Eastern European Time).

If you miss these streams, I upload all of them to Odysee so be sure to check them out! I am part of Odysee’s Viewer Rewards Program, so if you have an Odysee account, you can now get a daily watch reward of a bit of LBRY Credits (LBC) when you watch my videos, and I get some too!