Skip to main content

Overview

The Stream API service provides audio streaming functionality through a proxy layer that handles media URL resolution and stream delivery for Beat App. Base URL: ${STREAM_API} (resolves to ${API_URL}/api/stream) Configured in: src/constants.js

Configuration

src/constants.js
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000';
const STREAM_API = `${API_URL}/api/stream`;
const PROXY_URL = `${STREAM_API}/proxy?url=`;
VITE_API_URL
string
default:"http://localhost:3000"
Base URL for API services. The Stream API is mounted at /api/stream.

Proxy Endpoint

Stream Proxy

The primary streaming endpoint that proxies audio requests through the backend server. Endpoint: GET ${STREAM_API}/proxy
url
string
required
Encoded media URL to proxy. Typically a YouTube Music stream URL.
Response: Binary audio stream (audio/mp4, audio/webm, etc.)
import { PROXY_URL } from '@/constants';

// Construct proxied stream URL
const mediaUrl = 'https://rr1---sn-xxxx.googlevideo.com/videoplayback?...';
const streamUrl = `${PROXY_URL}${encodeURIComponent(mediaUrl)}`;

// Use in audio element
const audio = new Audio(streamUrl);
audio.play();

Purpose & Architecture

Why Proxy Streaming?

The Stream API proxy serves several critical functions:
  1. CORS Bypass: YouTube Music stream URLs have strict CORS policies that prevent direct browser playback from different origins. The proxy server adds appropriate CORS headers.
  2. URL Expiration: Direct stream URLs from YouTube Music expire quickly. The proxy can handle re-authentication and URL refresh transparently.
  3. Request Headers: Stream requests require specific headers and authentication that can’t be set from browser audio elements. The proxy manages these headers server-side.
  4. Rate Limiting: Centralizes stream requests through a single endpoint, allowing for rate limiting and monitoring.

Request Flow

Response Headers

The proxy endpoint should set appropriate headers for streaming:
Content-Type
string
Media type of the audio stream (e.g., audio/mp4, audio/webm)
Accept-Ranges
string
Indicates server supports range requests (typically bytes)
Content-Length
string
Total size of the audio file in bytes
Access-Control-Allow-Origin
string
CORS header allowing browser access (e.g., * or specific origin)
Cache-Control
string
Caching directives for the stream

Error Handling

Common Error Scenarios

Client-Side Error Handling

const audio = new Audio();

audio.addEventListener('error', (e) => {
  const error = audio.error;
  
  switch (error.code) {
    case MediaError.MEDIA_ERR_NETWORK:
      console.error('Network error while streaming');
      // Retry with exponential backoff
      break;
    case MediaError.MEDIA_ERR_DECODE:
      console.error('Error decoding audio stream');
      break;
    case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
      console.error('Stream format not supported');
      break;
    default:
      console.error('Unknown streaming error:', error);
  }
});

audio.src = `${PROXY_URL}${encodeURIComponent(streamUrl)}`;

Performance Considerations

Range Requests

The proxy should support HTTP range requests to enable:
  • Seeking within tracks
  • Efficient bandwidth usage
  • Progressive loading
// Browser automatically sends Range header when seeking
audio.currentTime = 60; // Seek to 1 minute
// Request: Range: bytes=2000000-

Caching Strategy

While stream URLs expire, partial caching can improve performance:
  • Short-term cache: 5-10 minutes for active streams
  • CDN edge caching: For popular tracks
  • Client-side buffering: Browser handles automatically

Streaming Protocols

YouTube Music typically provides:
  • Adaptive formats: Multiple quality levels
  • Codecs: AAC (audio/mp4), Opus (audio/webm)
  • Bitrates: 128kbps to 256kbps

Security Considerations

URL Validation

The proxy server should validate that URLs:
  • Point to legitimate YouTube Music domains
  • Don’t attempt to proxy arbitrary external URLs
  • Include proper authentication tokens
// Server-side validation example
function isValidStreamUrl(url) {
  try {
    const parsed = new URL(url);
    const allowedHosts = [
      'googlevideo.com',
      'youtube.com',
      'ytimg.com'
    ];
    return allowedHosts.some(host => parsed.hostname.endsWith(host));
  } catch {
    return false;
  }
}

Rate Limiting

Implement rate limiting to prevent abuse:
  • Per-IP limits: 100 requests per minute
  • Per-user limits: 500 requests per hour
  • Concurrent stream limits: 3 simultaneous streams

Integration with YouTube API

The Stream API works in conjunction with the YouTube API service:
  1. Get track metadata via YouTube API (see YouTube API docs)
  2. Extract stream URL from track data
  3. Proxy stream through Stream API
  4. Play audio in browser
import { searchTracks } from '@/services/youtube-api';
import { PROXY_URL } from '@/constants';

// 1. Search for track
const { tracks } = await searchTracks('never gonna give you up');
const track = tracks[0];

// 2. Get stream URL (would come from additional API call)
const streamUrl = await getTrackStreamUrl(track.trackId);

// 3. Proxy and play
const audio = new Audio(`${PROXY_URL}${encodeURIComponent(streamUrl)}`);
audio.play();

Environment Variables

VITE_API_URL
string
default:"http://localhost:3000"
Base URL for the API server hosting the stream proxy

Best Practices

  1. Always encode URLs: Use encodeURIComponent() when constructing proxy URLs
  2. Handle errors gracefully: Implement retry logic for network failures
  3. Preload next track: Start loading the next track 10-15 seconds before current ends
  4. Monitor stream health: Track buffering events and error rates
  5. Implement fallbacks: Have alternative quality streams available

Example: Complete Audio Player

import { PROXY_URL } from '@/constants';

class StreamPlayer {
  constructor() {
    this.audio = new Audio();
    this.setupEventListeners();
  }
  
  setupEventListeners() {
    this.audio.addEventListener('error', this.handleError.bind(this));
    this.audio.addEventListener('waiting', () => console.log('Buffering...'));
    this.audio.addEventListener('canplay', () => console.log('Ready to play'));
    this.audio.addEventListener('ended', () => this.playNext());
  }
  
  async playTrack(streamUrl) {
    try {
      const proxiedUrl = `${PROXY_URL}${encodeURIComponent(streamUrl)}`;
      this.audio.src = proxiedUrl;
      await this.audio.play();
    } catch (error) {
      console.error('Playback failed:', error);
      this.handleError(error);
    }
  }
  
  handleError(error) {
    // Implement retry logic
    console.error('Stream error:', error);
    // Could fetch a new stream URL and retry
  }
  
  playNext() {
    // Implement queue logic
  }
}

const player = new StreamPlayer();
export default player;

YouTube API

Get track metadata and stream URLs

API Overview

Backend architecture overview

Build docs developers (and LLMs) love