Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Arrow up icon
GO TO TOP
RxJS Cookbook for Reactive Programming

You're reading from   RxJS Cookbook for Reactive Programming Discover 40+ real-world solutions for building async, event-driven web apps

Arrow left icon
Product type Paperback
Published in Mar 2025
Publisher Packt
ISBN-13 9781788624053
Length 310 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Nikola Mitrovic Nikola Mitrovic
Author Profile Icon Nikola Mitrovic
Nikola Mitrovic
Arrow right icon
View More author details
Toc

Table of Contents (13) Chapters Close

Preface 1. Handling Errors and Side Effects in RxJS 2. Building User Interfaces with RxJS FREE CHAPTER 3. Understanding Reactive Animation Systems with RxJS 4. Testing RxJS Applications 5. Performance Optimizations with RxJS 6. Building Reactive State Management Systems with RxJS 7. Building Progressive Web Apps with RxJS 8. Building Offline-First Applications with RxJS 9. Going Real-Time with RxJS 10. Building Reactive NestJS Microservices with RxJS 11. Index
12. Other Books You May Enjoy

Crafting your perfect audio player using flexible RxJS controls

Everybody likes music. Whether you use Spotify, Deezer, YouTube, or something else to listen to your favorite jams, having control over your playlist with a sophisticated audio player is one of the essential conditions for providing an awesome user experience. In this recipe, we’ll create a lightweight RxJS audio player with reactive controls for playing and pausing songs, controlling volume, as well as skipping to the next song in the playlist.

How to do it…

The essential thing to understand in this recipe is the native HTMLAudioElement and, based on that, which events are the most important to react to.

Step 1 – Creating audio player events

In our audio-player.component.html file, we must implement markup for the audio player:

<audio #audio></audio>

Concerning that audio HTML element, in the component audio-player.component.ts file, we’ll define all the key events for that element:

@ViewChild('audio') audioElement!:
    ElementRef<HTMLAudioElement>;
ngAfterViewInit(): void {
    const audio = this.audioElement.nativeElement;
    const duration$ = fromEvent(audio,  
        'loadedmetadata').pipe(map(() => (
            { duration: audio.duration }))
    );
    const playPauseClick$ = fromEvent(audio, 'play').pipe(
        map(() => ({ isPlaying: true }))
    );
    const pauseClick$ = fromEvent(audio, 'pause').pipe(
        map(() => ({ isPlaying: false }))
    );
    const volumeChange$ = fromEvent(audio,
        'volumechange').pipe(
             map(() => ({ volume: audio.volume })),
    );
    const time$ = fromEvent(audio, 'timeupdate').pipe(
        map(() => ({ time: audio.currentTime }))
    );
    const error$ = fromEvent(audio, 'error');
}

Using the audio element, we can react to play, pause, volumechange, and timeupdate events, as well as metadata that holds information about the duration value of a song. Also, in case network interruptions occur when we fetch the audio file or corrupted audio files, we can subscribe to the error event from the audio element.

Now, we can combine all those events and hold the state of a song in a centralized place:

merge(
    duration$,
    playPauseClick$,
    pauseClick$,
    volumeChange$
).subscribe((state) =>
    this.audioService.updateState(state));

Step 2 – Managing song state

In our audio.service.ts file, we’ll store the state of the current song:

public audioState$ = new BehaviorSubject<AudioState>({
    isPlaying: false,
    volume: 0.5,
    currentTrackIndex: 0,
    duration: 0,
    tracks: []
});
updateState(state: Partial<AudioState>): void {
    this.audioState$.next({
     ...this.audioState$.value,
     ...state
    });
}

Now, we can subscribe to all state changes in the component and have reactive audio player controls over user actions.

Step 3 – Playing/pausing a song

Back in our audio-player.component.ts file, whenever play or pause events are being emitted, the state will update, at which point we can subscribe to the state change:

this.audioService.audioState$.subscribe(({ isPlaying }) =>
    this.isPlaying = isPlaying;
);

Now, in the audio-player.component.html file, we can present either a play or pause icon based on the following condition:

<button mat-fab class="play-pause-btn" (click)="playPause()">
    @if (isPlaying) {
        <mat-icon>pause</mat-icon>
    } @else {
        <mat-icon>play_arrow</mat-icon>
    }
</button>

We can also control the audio when playing a song:

playPause(): void {
    if (!this.isPlaying) {
        this.audioElement.nativeElement.play();
    } else {
        this.audioElement.nativeElement.pause();
    }
}

Step 4 – Controlling the song’s volume

By subscribing to the audio player state, we also have information about the volume based on the previously emitted volumechange event:

this.audioService.audioState$.subscribe(({ volume }) => {
    this.volume = volume;
});

We can represent this state in the UI like so:

<div class="volume">
    @if (volume === 0) {
        <mat-icon>volume_off</mat-icon>
    } @else {
        <mat-icon>volume_up</mat-icon>
    }
    <input
        type="range"
        [value]="volume"
        min="0"
        max="1"
        step="0.01"   
        (input)="changeVolume($event)"
    />
</div>

Now, we can emit the same event by changing the volume of the audio player by invoking the changeVolume() method:

changeVolume({ target: { value } }): void {
    this.audioElement.nativeElement.volume = value;
}

This will automatically update the volume state reactively on the audio player element.

Step 5 – Switching songs

Back in our audio.service.ts file, we’ve implemented methods for changing the current song index in the list of tracks:

previousSong(): void {
    let prevIndex =
        this.audioState$.value.currentTrackIndex - 1;
    const tracks = this.audioState$.value.tracks;
    if (prevIndex < 0) {
        prevIndex = tracks.length - 1; // Loop back to the
                                       // end
    }
    this.updateState({
        isPlaying: false,
        currentTrackIndex: prevIndex
    });
}
nextSong(): void {
    let nextIndex =
        this.audioState$.value.currentTrackIndex + 1;
    const tracks = this.audioState$.value.tracks;
    if (nextIndex >= tracks.length) {
        nextIndex = 0; // Loop back to the beginning
    }
    this.updateState({
        isPlaying: false,
        currentTrackIndex: nextIndex
    });
}

Also, when we come to the end of the list, we’ll loop to the beginning of the playlist.

Inside the audio-player.component.ts component, we can subscribe to this state change and change the song using the audio element:

this.audioService.audioState$.subscribe(({
    currentTrackIndex,
    tracks
}) => {
    if (
        tracks[currentTrackIndex].title !==
            this.currentTrack.title
    ) {
        this.audioElement.nativeElement.src =
            tracks[currentTrackIndex].song;
        this.currentTrack = tracks[currentTrackIndex];
    }
});

This means that we have all the information we need about the current song, which means we can display that data in our audio-player.component.html template.

Step 6 – Skipping to the middle of a song

In our audio element, there’s a timeupdate event that lets us track and update the current time of a song:

const time$ = fromEvent(audio, 'timeupdate').pipe(
    map(() => ({ time: audio.currentTime }))
);
time$.subscribe(({ time }) => this.currentTime = time);

In the UI, we can combine this current time information with the previous song metadata, show it in a slider, and watch the song progress:

<p>{{ currentTime | time }}</p>
<audio #audio></audio>
<mat-slider [max]="duration" class="song">
    <input matSliderThumb
    [value]="currentTime"
    (dragEnd)="skip($event)"
    >
</mat-slider>
<p>{{ duration | time }}</p>

Finally, if we open our browser, we can inspect all these features and play our favorite jam:

Figure 2.10: Reactive audio player

Figure 2.10: Reactive audio player

See also

You have been reading a chapter from
RxJS Cookbook for Reactive Programming
Published in: Mar 2025
Publisher: Packt
ISBN-13: 9781788624053
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime