Skip to content

Dynamic Music System with iTunes API #6

Description

@pkdev0101

PeppaMusic.js Feature Implementation

Overview

This feature implements a dynamic background music system using the iTunes Search API. It fetches a playable preview, handles browser autoplay restrictions, and provides a toggle button for user control.


Full Code

console.log("PeppaMusic.js loaded from _projects/PeppaPigGame/levels");

class PeppaMusic {
  constructor() {
    this.audio = null;
    this.started = false;
    this.isPlaying = false;
    this.endpoint = 'https://itunes.apple.com/search?term=peppa%20pig%20theme&entity=song&limit=10';
    this.userActivated = false;
    this.activateFromUserGesture = this.activateFromUserGesture.bind(this);
    this.createToggleButton();
  }

  createToggleButton() {
    const btn = document.createElement('button');
    btn.id = 'peppa-music-toggle';
    btn.innerHTML = 'Music';

    btn.style.cssText = `
      position: fixed;
      top: 10px;
      right: 10px;
      z-index: 10000;
      padding: 8px 16px;
      font-size: 14px;
      font-family: sans-serif;
      background: #ff6b9d;
      color: white;
      border: none;
      border-radius: 20px;
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(0,0,0,0.3);
    `;

    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      this.toggleMusic();
    });

    document.body.appendChild(btn);
    this.toggleBtn = btn;
  }

  async fetchPreviewUrl() {
    const response = await fetch(this.endpoint);
    if (!response.ok) {
      throw new Error('API request failed (' + response.status + ')');
    }

    const data = await response.json();
    const tracks = (data && Array.isArray(data.results)) ? data.results : [];

    const track = tracks.find(function(item) {
      return item && item.previewUrl;
    });

    if (!track || !track.previewUrl) {
      throw new Error('No playable preview URL found in API response');
    }

    return track.previewUrl;
  }

  async startMusic() {
    if (this.started || !this.userActivated) return;

    try {
      const previewUrl = await this.fetchPreviewUrl();
      this.audio = new Audio(previewUrl);
      this.audio.volume = 0.35;
      this.audio.loop = true;
      await this.audio.play();

      this.started = true;
      this.isPlaying = true;

      this.removeGestureListeners();
      this.updateButton();
    } catch (error) {
      console.warn('Failed to start music', error);
    }
  }

  stopMusic() {
    if (this.audio) {
      this.audio.pause();
      this.audio.currentTime = 0;
      this.isPlaying = false;
      this.updateButton();
    }
  }

  async toggleMusic() {
    if (!this.started) {
      this.userActivated = true;
      await this.startMusic();
    } else if (this.isPlaying) {
      this.stopMusic();
    } else {
      if (this.audio) {
        await this.audio.play();
        this.isPlaying = true;
        this.updateButton();
      }
    }
  }

  updateButton() {
    if (this.toggleBtn) {
      this.toggleBtn.innerHTML = this.isPlaying ? 'Music On' : 'Music Off';
    }
  }

  activateFromUserGesture() {
    this.userActivated = true;
    this.startMusic();
  }

  addGestureListeners() {
    window.addEventListener('click', this.activateFromUserGesture, { once: true });
    window.addEventListener('keydown', this.activateFromUserGesture, { once: true });
    window.addEventListener('touchstart', this.activateFromUserGesture, { once: true });
  }

  removeGestureListeners() {
    window.removeEventListener('click', this.activateFromUserGesture);
    window.removeEventListener('keydown', this.activateFromUserGesture);
    window.removeEventListener('touchstart', this.activateFromUserGesture);
  }
}

export default PeppaMusic;

Code Breakdown

Constructor

Initializes the music system by defining variables that track audio state, API configuration, and user interaction. It also binds the gesture handler and creates the toggle button immediately.

Button Creation (createToggleButton)

Creates a floating button on the screen that allows the user to control music. The button triggers the main control logic (toggleMusic) when clicked.

API Fetching (fetchPreviewUrl)

Connects to the iTunes Search API and retrieves a list of tracks. It selects the first valid track that contains a playable previewUrl and returns it for playback.

Music Start Logic (startMusic)

Handles the process of starting music. It ensures that music only starts after user interaction and only runs once. It creates the audio object, sets volume and looping, and begins playback.

Stop Logic (stopMusic)

Stops the audio playback and resets it to the beginning. It also updates the system state to reflect that music is no longer playing.

Toggle Logic (toggleMusic)

Acts as the main control system. It determines whether to start, stop, or resume music depending on the current state of the system.

UI Update (updateButton)

Updates the text of the toggle button to reflect whether music is currently playing or not.

Gesture Handling (activateFromUserGesture, addGestureListeners, removeGestureListeners)

Handles browser autoplay restrictions by detecting user interaction (click, keypress, or touch). Once interaction occurs, music is allowed to start, and listeners are removed afterward.

Image - Option to turn the music on or off Image

Summary

This system integrates an external API into a game environment while handling real-world constraints like autoplay restrictions. It provides a simple user interface for control and manages playback through clear state tracking, resulting in a smooth and interactive background music experience.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions