Type assertions

We are supposed to put type definitions on our variables in TypeScript. But I often had to resort to the frowned-upon type any. Here I initialize sng but then I cannot use it. It does know I am trying to use type ITEM for the shift method, but shift by definition can also return undefined and this definition of sng does not allow that. I get Type ‘ITEM | undefined’ is not assignable to type ITEM:

I would resort to this:

Yesterday I finally found a better solution. Use a type assertion on the shift:

Song-count badges in Egalitarian option

BADGES

Now when you turn on the Egalitarian option, artists that appear in a playlist more than once get a badge that shows their number of songs so far.

Here Counting Stars is the second song by Kool&Klean and A Summer Walk is the third. Cool Desert Night (Synth Remix) is the second song by Bullcrane:

SHUFFLE

With the Egalitarian option turned on, shuffle prioritizes not repeating artists. Badges wind up at the bottom.

If you shuffle more than once and are puzzled by what seems to be inconsistent results, you probably have songs with more than one artist. Here are two egalitarian shuffles of the same four-song playlist. All badges are at the bottom in both. Early placement of the two-artist song causes the first shuffle to start repeating artists at song three:

The second shuffle starts repeating artists at song four:

New playlist select page

I added track count to the playlist select cards and this got to be more text than fit nicely, so I changed the format of the cards, shrinking the cover art and positioning it on the left. While doing this I also fixed a bug that started because Spotify made an unexpected change to their API. Now Shufflizer can shuffle only music playlists. Playlists with other things, such as audiobooks, are refused.

Delete Song

Now each song has a Delete button. Click it to flag the song for deletion. The button can be clicked again to remove the flag. It is a toggle. If deleted songs are shuffled they get put at the end. Deletes commit and the songs vanish when the playlist is uploaded.

With this feature I prefer culling playlists in Shufflizer rather than in Spotify. I click Play Preview Clip to remind myself of the song, and then click Delete on songs I don’t want to keep.

The Delete button appears only on larger screen devices.

This version of Shufflizer brings again brings us to the current version of Angular, which is now 19.1.

Angular 17.2

New versions of Angular come out every few months. Every once in a while I regenerate Shufflizer to catch up. I want to make sure Shufflizer is not using depricated features. Shufflizer is Angular v17.2 now.

Drag-and-drop

Today there is new version of Shufflizer that offers drag-and-drop for repositioning of songs.

In Android and iOS there is a drag handle. It looks like an equal sign. This is so that dragging and scrolling don’t interfere with each other (dragging is so much like scrolling in these operating systems). In computer desktop operating systems like Windows, simply drag the song using any spot in the display of the song.

Transaction integrity improved

In my previous post, I proposed some enhancements to improve Shufflizer’s transaction integrity. These are done and in production now. They are:

  1. The count of songs is confirmed on download and upload
  2. A temporary backup copy of the playlist is uploaded when doing same-playlist upload, and then deleted only if the same-playlist upload succeeds

I just wound up doing more callbacks

Most of the enhancements needed procedural (aka. synchronous) processing. “Don’t do the following step, and don’t accept user input, until the current step finishes ok.” I looked for something in ReactiveX’s JavaScript implementation to do this (RXJS), but did not find one. Perhaps JavaScript’s await would have done it, but I realized that I could:

  • make steps run in series using the usual approach – callbacks
  • prevent user input with something I already had – the loading spinner

So I proceeded with additional layers of callbacks and moved my command that closes the loading spinner to the bottom layer.

What I mean by a callback is a procedure that is called after the Observable, which is asynchronous, returns a value. In this example deletePlaylist “calls back” to getPlaylists. getPlaylists does not run until deletePlaylist returns a value:


this.spotSvc.deletePlaylist(b_plId,accessToken).subscribe(()=>this.getPlaylists(userId, userCountry, accessToken));

Transaction integrity

We do not have ACID transaction integrity

Shufflizer is built on the Spotify Web API. This API does not provide ACID transaction integrity. If the user’s computer crashes in the middle of an upload, or if the user simply shuts the window in the middle of an upload, the uploaded playlist will be incomplete. If the user has two sessions going (ie. two separate windows on the same device or perhaps on different devices) and is making changes to playlists in each, it is possible that uploading could lose or duplicate songs.

When uploading to a new playlist these concerns are not a big deal, but Shufflizer’s default is to upload to the same playlist. Plus Shufflizer is meant for big playlists. The bigger the playlist the bigger the bummer if it gets ruined. (It is possible to enlist the help of Spotify technical support personnel to restore a playlist to a prior state, but obviously this is an undesirable last resort.)

Here are some things that Shufflizer could do to reduce risk

Playlist initial download:
  • After downloading a playlist for edit, confirm that the count of songs reported by Spotify agrees with the count of songs downloaded.

Same-playlist upload:
  1. first upload as a new playlist, creating a temporary backup copy
  2. then do the normal upload, rewriting the original playlist
  3. now confirm that the count of songs reported by Spotify agrees with the count of songs uploaded
  4. if all is well, delete the new playlist (the temporary backup copy)

With these measures in place, if something goes wrong the user will wind up with two versions of the playlist. Either the original playlist will be good or the backup version will be good, or even both could be good depending on the nature of the outage. It’s unlikely that both will be bad.

Why not just make Shufflizer always upload to a new playlist? Followers and clutter. Same-playlist upload retains a playlist’s followers. Same-playlist upload avoids the clutter of a great many similar playlists that would result from frequent use of Shufflizer.

ReactiveX Observables

I will begin working on these enhancements. I tagged this as a TypeScript and Angular blog entry because I think this will take me a bit deeper into ReactiveX techniques. Because I am working in Angular, all of the Spotify API REST interaction is accomplished with ReactiveX Observables.

New shuffle

Shufflizer has new shuffle algorithm. This is the action that occurs when you click a rand button.

The songs are shuffled in up to three sections. If there are any unlistened songs they are shuffled and put first. Then if there are listened songs they are split into two sections, each shuffled separately. One is less-recently listened songs and the other is more-recently listened songs.

If beenhere listen indicators are turned off then all songs are shuffled as unlistened songs.

Quietly

Shufflizer does not communicate this sectioning to the user. The rand button “just works.”

With the old algorithm, if the user was paying attention to the end of the list the user’s expectation that songs should be moving around when clicking rand was not met. Recently listened songs were pinned in a certain order at the end.

Now with the new algorithm more shuffling occurs. Shufflizer still puts recently listened songs at the end, but is less pathological about it.