In my previous blog post I described a flaw in my implementation of a session timer. Spotify doesn’t provide a way to monitor how much time is left but we know that the session lasts for sixty minutes, so I start my own sixty minute timer. My problem was that I was inadvertently creating more than one.
A singleton timer
My solution is to create a singleton timer, by subscribing only once. Instead of my class exposing the observable, it exposes the results – a simple numeric variable for the minutes remaining:
Observable.timer(0, 60000)
.map(mins => 60 - mins)
.takeWhile(minsRemaining => minsRemaining >= 0)
.subscribe(x => this.minsRemaining=x);
In the HTML, I rely on Angular to automatically detect changes to minsRemaining. Here is the button disable expression now. The async pipe subscribe is gone:
<button (click)="uploadToSpotify()" mat-raised-button
[disabled]="!playlistIsEdited()||minsRemaining<=0">
<i class="material-icons">file_upload</i>Upload to Spotify
</button>
I had two other subscribes, not just one more, as I thought in my previous post. I removed them. Now everything references minsRemaining:
<mat-card style="width:120px;text-align:center;"
[style.background-color]="minsRemaining <= 5 ? 'red' : 'transparent'">
<mat-card-content>Session minutes remaining</mat-card-content>
<mat-card-content style="font-size:30pt;padding-top:10px;padding-bottom:10px;">
{{minsRemaining}}
</mat-card-content>
</mat-card>
Survive not only page reloads, but also sleeping
With the above in place, I started working on how to keep it accurate through page reloads. I got something working, but then found that timer stalls when a PC goes to sleep. I’d wake up a sleeping PC and the timer was off by the number of minutes slept.
So I changed to examining time elapsed on the clock, rather than trying to make my process accurately tick each minute. The following code survives both page reloads and sleeping.
I am refreshing every five seconds now. This keeps my precision to within 5 seconds after page reload or sleeping. The 3600 is 60 minutes in seconds:
// sessionStorage survives page reloads
if (!sessionStorage.getItem(this.accessToken)) {
sessionStorage
.setItem(this.accessToken,Date.now().toString());
}
//Date.now()-startedAt survives sleeping
Observable.timer(0,5000)
.map(() => parseInt(sessionStorage.getItem(this.accessToken)))
.map(startedAt => 3600-(Date.now()-startedAt)/1000)
.map(secsRemaining => Math.ceil(secsRemaining/60))
.takeWhile(minsRemaining => minsRemaining >= 0)
.subscribe(x => this.minsRemaining=x);
expires_in=3600
Spotify indicates the number of seconds for the session in the url they return. It’s always been 3600 seconds, which is 60 minutes, but if they ever do return something different, I should honor it. So instead of hardcoding 3600, I parse it out of the url. This is not some sophisticated thing that I could tap into to find out how much time is left. It’s simply how many seconds total the token is good for. It stays the same even when the page is refreshed.
try {this.expiresIn=parseInt(
window.location.hash.match(/expires_in=(\d+)/)[1]
);}
catch (ERR) {this.expiresIn=0}
// sessionStorage survives page reloads
if (!sessionStorage.getItem(this.accessToken)) {
sessionStorage
.setItem(this.accessToken,Date.now().toString());
}
//Date.now()-startedAt survives sleeping
Observable.timer(0,5000)
.map(() => parseInt(sessionStorage.getItem(this.accessToken)))
.map(startedAt => this.expiresIn-(Date.now()-startedAt)/1000)
.map(secsRemaining => Math.ceil(secsRemaining/60))
.takeWhile(minsRemaining => minsRemaining >= 0)
.subscribe(x => this.minsRemaining=x);