How We Developed an Online Music Player with TypeScript

Article by:
Yauheni Svartsevich
4 min
In this article, we share our experience of creating a custom music player for a Social Music Web Platform with TypeScript illustrating each step with a programming code.

One of our clients, a Music Tech Company, came with a request to develop a web version of their Social Music Platform. They wanted to have an online music player that would provide advanced functionality and responsive design.

After interviewing the client and analyzing the requirements and documentation attached, we started to look for the solutions. We saw that most of them are over-complicated; therefore, we decided to develop a custom music player.

Disclaimer: in this article, we present code examples (not currently executing one)! Here, we would like to share our experience of creating a custom music player with TypeScript illustrating each step with programming code that could be written for a similar project.

Technology Used to Develop the Custom Music Player

Let's go over the tech stack that we used to create the custom music player. To deliver all player's features according to the requirements, we built the music player's core functionality on TypeScript

For UI development, we used React, Next.js, Redux-Toolkit, Material-UI.

For custom styling of Material-UI components, we used the CSS-in-JS approach.

Looking for a tech partner to build your product?

Reach out to Upsilon to discuss your project idea!

Let's Talk

Looking for a tech partner to build your product?

Reach out to Upsilon to discuss your project idea!

Let's Talk

Custom Music Player Development: Step-by-Step Guide

Step 1: Split the Project into Modules

Firstly, we split the project into modules. One of the modules works with the logic of track playback: play/pause, track length displaying, next/previous, volume control, etc. Also, please note that we used the publish-subscribe pattern and wrote a small implementation - pubsub.ts.


import { createPubSub } from './pubsub';

type AudioState = {
  duration: number;
  playing: boolean;
  volume: number;
};

export const createAudio = () => {
  const pubsub = createPubSub();
  const element = document.createElement('video');
  let currentTime = 0;

  let state: AudioState = {
    duration: 0,
    playing: false,
    volume: 0,
  };

  const setState = (value: Partial) => {
    state = { ...state, ...value };

    pubsub.publish('change', state);
  };

  const setup = () => {
    element.addEventListener('durationchange', () =>
      setState({ duration: element.duration }),
    );

    element.addEventListener('playing', () => setState({ playing: true }));

    element.addEventListener('pause', () => setState({ playing: false }));

    element.addEventListener('timeupdate', () => {
      const newCurrentTime = Math.round(element.currentTime);

      if (currentTime !== newCurrentTime) {
        currentTime = newCurrentTime;

        pubsub.publish('change-current-time', currentTime);
      }
    });

    element.addEventListener('volumechange', () =>
      setState({ volume: element.volume }),
    );

    setState({ volume: element.volume });
  };

  setup();

  return {
    seek(seconds: number) {
      element.currentTime = seconds;
      currentTime = seconds;

      pubsub.publish('change-current-time', currentTime);
    },

    getElement() {
      return element;
    },

    getState() {
      return state;
    },

    getCurrentTime() {
      return currentTime;
    },

    play() {
      element.play();
    },

    pause() {
      element.pause();
    },

    volume(value: number) {
      element.volume = value;
    },

    setUrl(url: string) {
      element.setAttribute('src', url);
      setState({ playing: false });
    },

    subscribe(listener: (newState: AudioState) => void) {
      return pubsub.subscribe('change', listener);
    },

    onChangeCurrentTime(listener: (newCurrentTime: number) => void) {
      return pubsub.subscribe('change-current-time', listener);
    },

    onEnded(listener: () => void) {
      element.addEventListener('ended', listener);

      return () => element.removeEventListener('ended', listener);
    },
  };
};

audio.ts

Another module includes the functionality of the track playback and playlists management. See the code example below:


import { createPubSub } from './pubsub';
import { createAudio } from './audio';

type Track = {
  url: string;
  title: string;
};

type State = AudioState & {
  tracks: Track[];
  currentTrack: Track | null;
  currentTrackIndex: number | null;
};

const createPlayer = () => {
  const pubsub = createPubSub();
  const audio = createAudio();

  let state: State = {
    ...audio.getState(),
    tracks: [],
    currentTrackIndex: null,
    currentTrack: null,
  };

  const setState = (value: Partial) => {
    state = { ...state, ...value };

    pubsub.publish('change', state);
  };

  audio.subscribe(setState);

  const changeTrack = () => {
    const track = state.currentTrack;

    if (track) {
      audio.setUrl(track.url);
      audio.play();
    }
  };

  const next = () => {
    if (state.currentTrackIndex === null) {
      return;
    }

    const lastIndex = state.tracks.length - 1;
    const newIndex = state.currentTrackIndex + 1;

    if (newIndex <= lastIndex) {
      setState({
        currentTrackIndex: newIndex,
        currentTrack: state.tracks[newIndex],
      });

      changeTrack();
    }
  };

  audio.onEnded(next);

  return {
    play: audio.play,
    pause: audio.pause,
    seek: audio.seek,
    volume: audio.volume,
    getCurrentTime: audio.getCurrentTime,
    getElement: audio.getElement,
    onChangeCurrentTime: audio.onChangeCurrentTime,

    getState() {
      return state;
    },

    setQueue(tracks: Track[]) {
      setState({ tracks });
    },

    playTrack(trackIndex: number) {
      setState({
        currentTrackIndex: trackIndex,
        currentTrack: state.tracks[trackIndex],
      });

      changeTrack();
    },

    next,

    prev() {
      if (state.currentTrackIndex === null) {
        return;
      }

      const newIndex = state.currentTrackIndex - 1;

      if (newIndex >= 0) {
        setState({
          currentTrack: state.tracks[newIndex],
          currentTrackIndex: newIndex,
        });

        changeTrack();
      }
    },

    subscribe(listener: (newState: State) => void) {
      return pubsub.subscribe('change', listener);
    },
  };
};

const player = createPlayer();

export default player;

player.ts

Step 2: React Component Usage

Here are several lines of code illustrating the React component that uses the player functionality shown above:




Step 3: Background Mode for Video Files

And here is an example of how a special player's background mode for video files can be written. In that mode, we can move from one screen (or page) to another, and the video continues to play without any interference.


.visuallyhidden {
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0);
  height: 1;
  width: 1;
  margin: -1;
  padding: 0;
  border: 0;
}


import React, { useRef, useEffect, FC } from 'react';

import player from './player';

const Video: FC = () => {
  const videoWrapperRef = useRef(null);

  useEffect(() => {
    const videoWrapperElement = videoWrapperRef.current!;
    const video = player.getElement();

    video.remove();
    video.classList.remove('visuallyhidden');
    videoWrapperElement.append(video);

    return () => {
      videoWrapperElement.removeChild(video);
      video.classList.add('visuallyhidden');
      document.body.append(video);
    };
  }, []);

  return 
; };

In this example, you can see: when a user switches the player from background to foreground mode, the video expands back, and the audio track continues to play "seamlessly" with no pauses.

Concluding Thoughts

After performing the steps above, we created a music player that further became the essential component of the client's Social Music Web Platform. You can learn more about it in the corresponding "Social Music Platform" case study and explore more code examples in the CodeSandbox in more detail.

If you have any questions regarding our custom software development services, don't hesitate to contact us and discuss you project ideas!

scroll
to top

Read Next

App Monetization Strategies: Which One to Choose in 2024?
Product Development

App Monetization Strategies: Which One to Choose in 2024?

12 min
Ways to Find Your Minimum Viable Audience
MVP

Ways to Find Your Minimum Viable Audience

8 min
Product Redesign: Reasons, Benefits, and Process
Product development

Product Redesign: Reasons, Benefits, and Process

15 min