Project Introduction
A comprehensive music streaming platform that allows users to listen to songs, create playlists, follow artists, and discover new music. This system includes user authentication, music library management, streaming functionality, and administrative controls. Perfect for building your own music streaming service similar to Spotify or Apple Music with core essential features.
✨ Features
User Features
- Music Streaming: Play, pause, seek through songs
- Playlist Creation: Create and manage custom playlists
- Favorites: Like and save favorite tracks
- Search: Search by song, artist, album, or genre
- Recently Played: Track listening history
- Offline Mode: Download songs for offline listening (premium)
- Queue Management: Create a play queue
Artist Features
- Artist Profile: Dedicated artist pages with bio and discography
- Music Upload: Upload songs and album artwork
- Analytics: Track plays, likes, and followers
- Fan Base: See listener demographics
Admin Features
- Content Management: Approve/reject uploaded music
- User Management: Manage user accounts and subscriptions
- Royalty Tracking: Track plays for royalty calculations
- Analytics Dashboard: Platform usage statistics
Technical Features
- Audio Streaming: Efficient streaming with range requests
- Caching: CDN integration for popular tracks
- Playlists: Dynamic and static playlist support
- Recommendations: Basic algorithm for suggested songs
- Responsive Design: Works on desktop and mobile
📁 File Structure
blog-website/ │ ├── music/ # Music streaming directory │ ├── index.php # Music homepage │ ├── browse.php # Browse music │ ├── search.php # Search results │ ├── playlist.php # Playlist view │ ├── playlist-create.php # Create playlist │ ├── artist.php # Artist profile │ ├── album.php # Album view │ ├── song.php # Single song view │ ├── library.php # User library │ ├── queue.php # Play queue │ │ │ ├── dashboard/ # User dashboard │ │ ├── index.php # Dashboard home │ │ ├── upload.php # Upload music │ │ ├── earnings.php # Artist earnings │ │ └── settings.php # Account settings │ │ │ ├── admin/ # Admin panel │ │ ├── dashboard.php # Admin dashboard │ │ ├── music.php # Music management │ │ ├── users.php # User management │ │ ├── royalties.php # Royalty tracking │ │ └── reports.php # Platform reports │ │ │ ├── includes/ # Core includes │ │ ├── music-config.php # Configuration │ │ ├── music-functions.php # Core functions │ │ ├── auth.php # Authentication │ │ ├── streaming.php # Streaming handler │ │ └── recommendations.php # Recommendation engine │ │ │ ├── css/ # Stylesheets │ │ ├── music.css # Main music styles │ │ └── player.css # Player interface │ │ │ ├── js/ # JavaScript │ │ ├── music.js # Core music JS │ │ ├── player.js # Audio player │ │ ├── playlist.js # Playlist management │ │ └── queue.js # Queue handling │ │ │ └── uploads/ # Media uploads │ ├── songs/ # Audio files │ ├── covers/ # Album artwork │ ├── avatars/ # Artist images │ └── temp/ # Temporary files │ ├── .env # Environment variables ├── composer.json # Composer dependencies │ └── database/ └── music.sql # Database schema
🗄️ Database Schema (database/music.sql)
-- Create music database
CREATE DATABASE IF NOT EXISTS music_db;
USE music_db;
-- Users table
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
username VARCHAR(100) UNIQUE NOT NULL,
full_name VARCHAR(255),
user_type ENUM('listener', 'artist', 'admin') DEFAULT 'listener',
subscription_type ENUM('free', 'premium', 'family') DEFAULT 'free',
subscription_expires TIMESTAMP NULL,
profile_image VARCHAR(500),
cover_image VARCHAR(500),
bio TEXT,
location VARCHAR(255),
website VARCHAR(500),
is_verified BOOLEAN DEFAULT FALSE,
is_active BOOLEAN DEFAULT TRUE,
last_active TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_type (user_type),
INDEX idx_subscription (subscription_type),
FULLTEXT INDEX idx_search (username, full_name, bio)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Artists (extends users)
CREATE TABLE IF NOT EXISTS artists (
id INT PRIMARY KEY,
stage_name VARCHAR(255),
biography TEXT,
formed_year INT,
country VARCHAR(100),
genres JSON,
social_links JSON,
total_plays BIGINT DEFAULT 0,
total_followers INT DEFAULT 0,
monthly_listeners INT DEFAULT 0,
INDEX idx_plays (total_plays),
FOREIGN KEY (id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Genres
CREATE TABLE IF NOT EXISTS genres (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
color VARCHAR(20),
image VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Albums
CREATE TABLE IF NOT EXISTS albums (
id INT AUTO_INCREMENT PRIMARY KEY,
artist_id INT NOT NULL,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) UNIQUE NOT NULL,
description TEXT,
cover_image VARCHAR(500),
release_date DATE,
genre_id INT,
type ENUM('album', 'ep', 'single', 'compilation') DEFAULT 'album',
label VARCHAR(255),
total_tracks INT DEFAULT 0,
total_duration INT DEFAULT 0, -- in seconds
total_plays BIGINT DEFAULT 0,
is_featured BOOLEAN DEFAULT FALSE,
is_explicit BOOLEAN DEFAULT FALSE,
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_artist (artist_id),
INDEX idx_genre (genre_id),
INDEX idx_release (release_date),
FULLTEXT INDEX idx_search (title, description),
FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE,
FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Songs
CREATE TABLE IF NOT EXISTS songs (
id INT AUTO_INCREMENT PRIMARY KEY,
artist_id INT NOT NULL,
album_id INT NULL,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) UNIQUE NOT NULL,
duration INT NOT NULL, -- in seconds
track_number INT,
disc_number INT DEFAULT 1,
file_path VARCHAR(500) NOT NULL,
file_size INT NOT NULL, -- in bytes
file_format VARCHAR(10) NOT NULL,
bitrate INT,
waveform_data JSON,
lyrics TEXT,
genre_id INT,
mood VARCHAR(100),
bpm INT,
key_signature VARCHAR(10),
is_explicit BOOLEAN DEFAULT FALSE,
is_instrumental BOOLEAN DEFAULT FALSE,
is_featured BOOLEAN DEFAULT FALSE,
play_count BIGINT DEFAULT 0,
like_count INT DEFAULT 0,
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
release_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_artist (artist_id),
INDEX idx_album (album_id),
INDEX idx_genre (genre_id),
INDEX idx_plays (play_count),
INDEX idx_status (status),
FULLTEXT INDEX idx_search (title, lyrics),
FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE,
FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE SET NULL,
FOREIGN KEY (genre_id) REFERENCES genres(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Featured artists on songs (featuring)
CREATE TABLE IF NOT EXISTS song_features (
id INT AUTO_INCREMENT PRIMARY KEY,
song_id INT NOT NULL,
artist_id INT NOT NULL,
feature_order INT DEFAULT 1,
UNIQUE KEY unique_feature (song_id, artist_id),
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE,
FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Playlists
CREATE TABLE IF NOT EXISTS playlists (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL,
description TEXT,
cover_image VARCHAR(500),
is_public BOOLEAN DEFAULT TRUE,
is_collaborative BOOLEAN DEFAULT FALSE,
play_count INT DEFAULT 0,
follower_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
INDEX idx_public (is_public),
FULLTEXT INDEX idx_search (name, description),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Playlist songs
CREATE TABLE IF NOT EXISTS playlist_songs (
id INT AUTO_INCREMENT PRIMARY KEY,
playlist_id INT NOT NULL,
song_id INT NOT NULL,
added_by INT NOT NULL,
position INT NOT NULL,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_position (playlist_id, position),
FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE,
FOREIGN KEY (added_by) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- User library (favorites)
CREATE TABLE IF NOT EXISTS user_library (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
song_id INT NOT NULL,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_library (user_id, song_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Following artists
CREATE TABLE IF NOT EXISTS user_follows (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
artist_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_follow (user_id, artist_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (artist_id) REFERENCES artists(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Listening history
CREATE TABLE IF NOT EXISTS listening_history (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
song_id INT NOT NULL,
played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
duration_played INT, -- seconds actually played
completed BOOLEAN DEFAULT FALSE,
source VARCHAR(50), -- playlist, search, library, etc.
ip_address VARCHAR(45),
user_agent TEXT,
INDEX idx_user (user_id),
INDEX idx_song (song_id),
INDEX idx_played (played_at),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- User play queue
CREATE TABLE IF NOT EXISTS play_queue (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
song_id INT NOT NULL,
position INT NOT NULL,
context VARCHAR(50), -- playlist, album, search, etc.
context_id INT,
added_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user (user_id),
UNIQUE KEY unique_position (user_id, position),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- User ratings
CREATE TABLE IF NOT EXISTS song_ratings (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
song_id INT NOT NULL,
rating TINYINT CHECK (rating >= 1 AND rating <= 5),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_rating (user_id, song_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Royalties tracking
CREATE TABLE IF NOT EXISTS royalties (
id INT AUTO_INCREMENT PRIMARY KEY,
song_id INT NOT NULL,
artist_id INT NOT NULL,
period_start DATE NOT NULL,
period_end DATE NOT NULL,
total_plays INT DEFAULT 0,
rate_per_play DECIMAL(10,6) DEFAULT 0.004, -- $0.004 per play
amount DECIMAL(10,2) DEFAULT 0.00,
status ENUM('pending', 'paid', 'cancelled') DEFAULT 'pending',
paid_at TIMESTAMP NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_song (song_id),
INDEX idx_artist (artist_id),
INDEX idx_period (period_start, period_end),
FOREIGN KEY (song_id) REFERENCES songs(id) ON DELETE CASCADE,
FOREIGN KEY (artist_id) REFERENCES artists(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Reports and analytics
CREATE TABLE IF NOT EXISTS daily_stats (
id INT AUTO_INCREMENT PRIMARY KEY,
date DATE UNIQUE NOT NULL,
total_plays BIGINT DEFAULT 0,
unique_listeners INT DEFAULT 0,
new_users INT DEFAULT 0,
new_songs INT DEFAULT 0,
total_stream_time BIGINT DEFAULT 0, -- in seconds
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Insert default genres
INSERT INTO genres (name, slug, color) VALUES
('Pop', 'pop', '#FF69B4'),
('Rock', 'rock', '#CD5C5C'),
('Hip Hop', 'hip-hop', '#FFD700'),
('Electronic', 'electronic', '#00FFFF'),
('R&B', 'rnb', '#800080'),
('Country', 'country', '#8B4513'),
('Jazz', 'jazz', '#4B0082'),
('Classical', 'classical', '#DAA520'),
('Reggae', 'reggae', '#008000'),
('Folk', 'folk', '#A0522D'),
('Metal', 'metal', '#2F4F4F'),
('Blues', 'blues', '#4169E1');
-- Platform settings
CREATE TABLE IF NOT EXISTS platform_settings (
id INT AUTO_INCREMENT PRIMARY KEY,
setting_key VARCHAR(255) UNIQUE NOT NULL,
setting_value TEXT,
setting_type ENUM('text', 'number', 'boolean', 'json') DEFAULT 'text',
description TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert default settings
INSERT INTO platform_settings (setting_key, setting_value, setting_type, description) VALUES
('site_name', 'My Music Stream', 'text', 'Platform name'),
('free_tier_enabled', 'true', 'boolean', 'Enable free tier'),
('free_tier_skips_per_hour', '6', 'number', 'Number of skips per hour for free users'),
('free_tier_ad_frequency', '3', 'number', 'Ads frequency (per hour)'),
('premium_price_monthly', '9.99', 'number', 'Premium monthly price'),
('family_price_monthly', '14.99', 'number', 'Family plan monthly price'),
('artist_verification_required', 'false', 'boolean', 'Require verification for artists'),
('song_approval_required', 'true', 'boolean', 'Require admin approval for songs'),
('royalty_rate_per_play', '0.004', 'number', 'Royalty rate per play in USD'),
('min_payout_amount', '50', 'number', 'Minimum payout amount for artists'),
('max_upload_size', '10485760', 'number', 'Maximum upload size in bytes (10MB)'),
('allowed_audio_formats', '["mp3","wav","flac","m4a"]', 'json', 'Allowed audio formats');
🔧 Core Functions
1. includes/music-config.php
<?php
/**
* Music Streaming Configuration
*/
require_once __DIR__ . '/../../vendor/autoload.php';
use Dotenv\Dotenv;
// Load environment variables
$dotenv = Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load();
// Database configuration
define('DB_HOST', $_ENV['DB_HOST']);
define('DB_USER', $_ENV['DB_USER']);
define('DB_PASS', $_ENV['DB_PASS']);
define('DB_NAME', 'music_db');
// Platform settings
define('SITE_NAME', 'My Music Stream');
define('SITE_URL', $_ENV['APP_URL'] . '/music');
// Audio settings
define('MAX_UPLOAD_SIZE', 10 * 1024 * 1024); // 10MB
define('ALLOWED_AUDIO_FORMATS', ['mp3', 'wav', 'flac', 'm4a', 'aac']);
define('STREAM_CHUNK_SIZE', 8192); // 8KB chunks
define('CACHE_DURATION', 3600); // 1 hour
// Subscription features
define('FREE_TIER_ENABLED', true);
define('FREE_TIER_SKIPS_PER_HOUR', 6);
define('FREE_TIER_AD_FREQUENCY', 3); // ads per hour
define('PREMIUM_PRICE_MONTHLY', 9.99);
define('FAMILY_PRICE_MONTHLY', 14.99);
// Royalty settings
define('ROYALTY_RATE_PER_PLAY', 0.004); // $0.004 per play
define('MIN_PAYOUT_AMOUNT', 50); // Minimum $50 to withdraw
// Paths
define('UPLOAD_PATH', __DIR__ . '/../uploads/');
define('SONG_PATH', UPLOAD_PATH . 'songs/');
define('COVER_PATH', UPLOAD_PATH . 'covers/');
define('AVATAR_PATH', UPLOAD_PATH . 'avatars/');
define('TEMP_PATH', UPLOAD_PATH . 'temp/');
// Create directories if they don't exist
foreach ([SONG_PATH, COVER_PATH, AVATAR_PATH, TEMP_PATH] as $path) {
if (!file_exists($path)) {
mkdir($path, 0777, true);
}
}
// Start session
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
// Database connection
function getDB() {
static $pdo = null;
if ($pdo === null) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
die("Database connection failed. Please try again later.");
}
}
return $pdo;
}
// Get current user
function getCurrentUser() {
if (isset($_SESSION['user_id'])) {
$pdo = getDB();
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
return null;
}
// Check if user is logged in
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
// Require login
function requireLogin() {
if (!isLoggedIn()) {
header('Location: /blog-website/login.php?redirect=' . urlencode($_SERVER['REQUEST_URI']));
exit;
}
}
// Check subscription status
function hasPremium($user) {
if (!$user) return false;
if ($user['subscription_type'] === 'premium' || $user['subscription_type'] === 'family') {
if ($user['subscription_expires'] && strtotime($user['subscription_expires']) > time()) {
return true;
}
}
return false;
}
// Format duration (seconds to MM:SS)
function formatDuration($seconds) {
$minutes = floor($seconds / 60);
$seconds = $seconds % 60;
return sprintf("%d:%02d", $minutes, $seconds);
}
// Format play count
function formatPlays($count) {
if ($count >= 1000000) {
return round($count / 1000000, 1) . 'M';
}
if ($count >= 1000) {
return round($count / 1000, 1) . 'K';
}
return (string)$count;
}
2. music/index.php (Homepage)
<?php
require_once '../includes/config.php';
require_once 'includes/music-config.php';
require_once 'includes/music-functions.php';
$page_title = SITE_NAME . ' - Listen to Free Music';
$pdo = getDB();
// Get featured songs
$featuredSongs = $pdo->query("
SELECT s.*,
a.stage_name as artist_name,
al.title as album_title,
al.cover_image as album_cover
FROM songs s
JOIN artists a ON s.artist_id = a.id
LEFT JOIN albums al ON s.album_id = al.id
WHERE s.status = 'approved' AND s.is_featured = 1
ORDER BY s.created_at DESC
LIMIT 10
")->fetchAll();
// Get popular songs
$popularSongs = $pdo->query("
SELECT s.*,
a.stage_name as artist_name,
al.title as album_title,
al.cover_image as album_cover
FROM songs s
JOIN artists a ON s.artist_id = a.id
LEFT JOIN albums al ON s.album_id = al.id
WHERE s.status = 'approved'
ORDER BY s.play_count DESC
LIMIT 10
")->fetchAll();
// Get new releases
$newReleases = $pdo->query("
SELECT s.*,
a.stage_name as artist_name,
al.title as album_title,
al.cover_image as album_cover
FROM songs s
JOIN artists a ON s.artist_id = a.id
LEFT JOIN albums al ON s.album_id = al.id
WHERE s.status = 'approved'
ORDER BY s.created_at DESC
LIMIT 10
")->fetchAll();
// Get featured playlists
$featuredPlaylists = $pdo->query("
SELECT p.*, u.username as creator_name,
(SELECT COUNT(*) FROM playlist_songs WHERE playlist_id = p.id) as song_count
FROM playlists p
JOIN users u ON p.user_id = u.id
WHERE p.is_public = 1
ORDER BY p.play_count DESC
LIMIT 6
")->fetchAll();
// Get top artists
$topArtists = $pdo->query("
SELECT a.*, u.username, u.profile_image,
(SELECT COUNT(*) FROM songs WHERE artist_id = a.id) as song_count
FROM artists a
JOIN users u ON a.id = u.id
ORDER BY a.total_plays DESC
LIMIT 8
")->fetchAll();
// Get genres
$genres = $pdo->query("
SELECT * FROM genres
ORDER BY name
LIMIT 12
")->fetchAll();
include '../includes/header.php';
?>
<link rel="stylesheet" href="css/music.css">
<link rel="stylesheet" href="css/player.css">
<div class="music-container">
<!-- Hero Section -->
<section class="hero-section" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
<div class="hero-content">
<h1>Listen to Millions of Songs</h1>
<p class="hero-subtitle">Stream your favorite music for free. Create playlists, follow artists, and discover new music.</p>
<?php if (!isLoggedIn()): ?>
<div class="hero-buttons">
<a href="/blog-website/register.php" class="btn-primary">Sign Up Free</a>
<a href="/blog-website/subscribe.php" class="btn-secondary">Go Premium</a>
</div>
<?php endif; ?>
</div>
</section>
<!-- Player Bar (hidden initially) -->
<div id="player-bar" class="player-bar" style="display: none;">
<!-- Player will be loaded via JavaScript -->
</div>
<!-- Featured Songs -->
<section class="music-section">
<div class="section-header">
<h2>Featured Today</h2>
<a href="browse.php?filter=featured" class="view-all">View All →</a>
</div>
<div class="song-grid">
<?php foreach ($featuredSongs as $song): ?>
<div class="song-card" data-song-id="<?php echo $song['id']; ?>">
<div class="song-image">
<img src="<?php echo $song['album_cover'] ?? '/blog-website/assets/default-cover.jpg'; ?>"
alt="<?php echo htmlspecialchars($song['title']); ?>">
<button class="play-btn" onclick="playSong(<?php echo $song['id']; ?>)">
▶
</button>
</div>
<div class="song-info">
<h3 class="song-title">
<a href="song.php?id=<?php echo $song['id']; ?>">
<?php echo htmlspecialchars($song['title']); ?>
</a>
</h3>
<p class="song-artist">
<a href="artist.php?id=<?php echo $song['artist_id']; ?>">
<?php echo htmlspecialchars($song['artist_name']); ?>
</a>
</p>
<div class="song-meta">
<span class="song-plays"><?php echo formatPlays($song['play_count']); ?> plays</span>
<span class="song-duration"><?php echo formatDuration($song['duration']); ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<!-- Popular Songs -->
<section class="music-section">
<div class="section-header">
<h2>Most Popular</h2>
<a href="browse.php?filter=popular" class="view-all">View All →</a>
</div>
<div class="song-list">
<?php foreach ($popularSongs as $index => $song): ?>
<div class="song-list-item" data-song-id="<?php echo $song['id']; ?>">
<span class="song-rank"><?php echo $index + 1; ?></span>
<img src="<?php echo $song['album_cover'] ?? '/blog-website/assets/default-cover.jpg'; ?>"
alt="" class="song-thumb">
<div class="song-details">
<h4><?php echo htmlspecialchars($song['title']); ?></h4>
<p><?php echo htmlspecialchars($song['artist_name']); ?></p>
</div>
<span class="song-plays"><?php echo formatPlays($song['play_count']); ?></span>
<span class="song-duration"><?php echo formatDuration($song['duration']); ?></span>
<button class="song-play" onclick="playSong(<?php echo $song['id']; ?>)">▶</button>
<button class="song-more" onclick="showSongMenu(<?php echo $song['id']; ?>)">⋯</button>
</div>
<?php endforeach; ?>
</div>
</section>
<!-- Genres -->
<section class="genres-section">
<h2>Browse by Genre</h2>
<div class="genres-grid">
<?php foreach ($genres as $genre): ?>
<a href="browse.php?genre=<?php echo $genre['slug']; ?>" class="genre-card"
style="background: linear-gradient(135deg, <?php echo $genre['color']; ?> 0%, #333 100%);">
<span class="genre-name"><?php echo htmlspecialchars($genre['name']); ?></span>
</a>
<?php endforeach; ?>
</div>
</section>
<!-- Featured Playlists -->
<section class="music-section">
<div class="section-header">
<h2>Featured Playlists</h2>
<a href="browse.php?filter=playlists" class="view-all">View All →</a>
</div>
<div class="playlist-grid">
<?php foreach ($featuredPlaylists as $playlist): ?>
<div class="playlist-card">
<div class="playlist-image">
<img src="<?php echo $playlist['cover_image'] ?? '/blog-website/assets/default-playlist.jpg'; ?>"
alt="<?php echo htmlspecialchars($playlist['name']); ?>">
<button class="play-playlist" onclick="playPlaylist(<?php echo $playlist['id']; ?>)">
▶
</button>
</div>
<div class="playlist-info">
<h3>
<a href="playlist.php?id=<?php echo $playlist['id']; ?>">
<?php echo htmlspecialchars($playlist['name']); ?>
</a>
</h3>
<p>By <?php echo htmlspecialchars($playlist['creator_name']); ?></p>
<span class="playlist-meta">
<?php echo $playlist['song_count']; ?> songs
</span>
</div>
</div>
<?php endforeach; ?>
</div>
</section>
<!-- Top Artists -->
<section class="music-section">
<div class="section-header">
<h2>Top Artists</h2>
<a href="browse.php?filter=artists" class="view-all">View All →</a>
</div>
<div class="artists-grid">
<?php foreach ($topArtists as $artist): ?>
<div class="artist-card">
<img src="<?php echo $artist['profile_image'] ?? '/blog-website/assets/default-artist.jpg'; ?>"
alt="<?php echo htmlspecialchars($artist['username']); ?>">
<h4>
<a href="artist.php?id=<?php echo $artist['id']; ?>">
<?php echo htmlspecialchars($artist['stage_name'] ?? $artist['username']); ?>
</a>
</h4>
<p><?php echo formatPlays($artist['total_plays']); ?> monthly listeners</p>
<button class="btn-follow" onclick="followArtist(<?php echo $artist['id']; ?>)">
Follow
</button>
</div>
<?php endforeach; ?>
</div>
</section>
</div>
<!-- Song Menu Modal -->
<div id="songMenu" class="modal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3>Song Options</h3>
<span class="close" onclick="closeModal()">×</span>
</div>
<div class="modal-body">
<button onclick="addToQueue()">Add to Queue</button>
<button onclick="addToPlaylist()">Add to Playlist</button>
<button onclick="addToLibrary()">Save to Library</button>
<button onclick="shareSong()">Share</button>
<?php if (isLoggedIn() && hasPremium(getCurrentUser())): ?>
<button onclick="downloadSong()">Download</button>
<?php endif; ?>
</div>
</div>
</div>
<script src="js/music.js"></script>
<script src="js/player.js"></script>
<script>
// Initialize player when page loads
document.addEventListener('DOMContentLoaded', function() {
initPlayer();
});
function playSong(songId) {
// Load and play song
fetch('ajax/play-song.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'song_id=' + songId
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadPlayer(data.song);
showPlayer();
}
});
}
function playPlaylist(playlistId) {
fetch('ajax/play-playlist.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'playlist_id=' + playlistId
})
.then(response => response.json())
.then(data => {
if (data.success) {
loadPlaylistPlayer(data.songs, data.playlist);
showPlayer();
}
});
}
function followArtist(artistId) {
<?php if (!isLoggedIn()): ?>
window.location.href = '/blog-website/login.php?redirect=' + encodeURIComponent(window.location.href);
return;
<?php endif; ?>
fetch('ajax/follow-artist.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'artist_id=' + artistId
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Artist followed!');
}
});
}
let currentSongId = null;
function showSongMenu(songId) {
currentSongId = songId;
document.getElementById('songMenu').style.display = 'block';
}
function closeModal() {
document.getElementById('songMenu').style.display = 'none';
}
function addToQueue() {
if (!currentSongId) return;
fetch('ajax/add-to-queue.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'song_id=' + currentSongId
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Added to queue');
closeModal();
}
});
}
function addToPlaylist() {
if (!currentSongId) return;
window.location.href = 'playlist-create.php?song_id=' + currentSongId;
}
function addToLibrary() {
if (!currentSongId) return;
fetch('ajax/add-to-library.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'song_id=' + currentSongId
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Added to library');
closeModal();
}
});
}
function shareSong() {
if (!currentSongId) return;
// Copy link to clipboard
const url = window.location.origin + '/blog-website/music/song.php?id=' + currentSongId;
navigator.clipboard.writeText(url).then(() => {
alert('Link copied to clipboard!');
closeModal();
});
}
function downloadSong() {
if (!currentSongId) return;
window.location.href = 'download.php?song_id=' + currentSongId;
}
</script>
<style>
.hero-section {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4rem 2rem;
text-align: center;
border-radius: 10px;
margin-bottom: 3rem;
}
.hero-content h1 {
font-size: 3rem;
margin-bottom: 1rem;
}
.hero-subtitle {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.hero-buttons {
display: flex;
gap: 1rem;
justify-content: center;
}
.btn-primary, .btn-secondary {
padding: 1rem 2rem;
border-radius: 5px;
text-decoration: none;
font-weight: 500;
transition: transform 0.3s;
}
.btn-primary {
background: white;
color: #667eea;
}
.btn-secondary {
background: rgba(255,255,255,0.2);
color: white;
}
.btn-primary:hover, .btn-secondary:hover {
transform: translateY(-2px);
}
.music-section {
margin-bottom: 4rem;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.section-header h2 {
font-size: 1.8rem;
color: #333;
}
.view-all {
color: #667eea;
text-decoration: none;
font-weight: 500;
}
.song-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1.5rem;
}
.song-card {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
transition: transform 0.3s;
}
.song-card:hover {
transform: translateY(-5px);
}
.song-image {
position: relative;
aspect-ratio: 1 / 1;
}
.song-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-btn {
position: absolute;
bottom: 10px;
right: 10px;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s, transform 0.3s;
}
.song-card:hover .play-btn {
opacity: 1;
}
.play-btn:hover {
transform: scale(1.1);
}
.song-info {
padding: 1rem;
}
.song-title {
font-size: 1rem;
margin-bottom: 0.3rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.song-title a {
color: #333;
text-decoration: none;
}
.song-artist {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.song-artist a {
color: #666;
text-decoration: none;
}
.song-artist a:hover {
color: #667eea;
}
.song-meta {
display: flex;
justify-content: space-between;
font-size: 0.8rem;
color: #888;
}
/* Song List */
.song-list {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.song-list-item {
display: flex;
align-items: center;
padding: 0.8rem 1rem;
border-bottom: 1px solid #eee;
transition: background 0.3s;
}
.song-list-item:hover {
background: #f8f9fa;
}
.song-rank {
width: 40px;
font-weight: bold;
color: #888;
}
.song-thumb {
width: 40px;
height: 40px;
border-radius: 5px;
object-fit: cover;
margin-right: 1rem;
}
.song-details {
flex: 1;
}
.song-details h4 {
font-size: 1rem;
margin-bottom: 0.2rem;
color: #333;
}
.song-details p {
font-size: 0.9rem;
color: #666;
}
.song-plays {
width: 80px;
color: #888;
font-size: 0.9rem;
}
.song-duration {
width: 50px;
color: #888;
font-size: 0.9rem;
}
.song-play, .song-more {
width: 30px;
height: 30px;
border: none;
background: none;
cursor: pointer;
color: #888;
font-size: 1.1rem;
margin-left: 0.5rem;
}
.song-play:hover {
color: #667eea;
}
.song-more:hover {
color: #333;
}
/* Genres Grid */
.genres-section {
margin-bottom: 4rem;
}
.genres-section h2 {
font-size: 1.8rem;
color: #333;
margin-bottom: 1.5rem;
}
.genres-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.genre-card {
aspect-ratio: 2 / 1;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
color: white;
font-weight: bold;
font-size: 1.2rem;
transition: transform 0.3s;
}
.genre-card:hover {
transform: scale(1.05);
}
/* Playlist Grid */
.playlist-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1.5rem;
}
.playlist-card {
background: white;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
.playlist-image {
position: relative;
aspect-ratio: 1 / 1;
}
.playlist-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-playlist {
position: absolute;
bottom: 10px;
right: 10px;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
}
.playlist-card:hover .play-playlist {
opacity: 1;
}
.playlist-info {
padding: 1rem;
}
.playlist-info h3 {
font-size: 1rem;
margin-bottom: 0.3rem;
}
.playlist-info h3 a {
color: #333;
text-decoration: none;
}
.playlist-info p {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.3rem;
}
.playlist-meta {
font-size: 0.8rem;
color: #888;
}
/* Artists Grid */
.artists-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1.5rem;
}
.artist-card {
text-align: center;
}
.artist-card img {
width: 150px;
height: 150px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 1rem;
}
.artist-card h4 {
margin-bottom: 0.3rem;
}
.artist-card h4 a {
color: #333;
text-decoration: none;
}
.artist-card p {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.btn-follow {
padding: 0.5rem 1rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
transition: opacity 0.3s;
}
.btn-follow:hover {
opacity: 0.9;
}
/* Player Bar */
.player-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: white;
box-shadow: 0 -2px 20px rgba(0,0,0,0.1);
padding: 1rem;
z-index: 1000;
}
/* Modal */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
z-index: 2000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 10px;
width: 400px;
max-width: 90%;
}
.modal-header {
padding: 1rem;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.close {
font-size: 1.5rem;
cursor: pointer;
color: #888;
}
.close:hover {
color: #333;
}
.modal-body {
padding: 1rem;
}
.modal-body button {
width: 100%;
padding: 0.8rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
border: none;
border-radius: 5px;
cursor: pointer;
text-align: left;
transition: background 0.3s;
}
.modal-body button:hover {
background: #e9ecef;
}
/* Responsive */
@media (max-width: 768px) {
.hero-content h1 {
font-size: 2rem;
}
.hero-buttons {
flex-direction: column;
}
.song-list-item {
flex-wrap: wrap;
}
.song-details {
width: 100%;
margin: 0.5rem 0;
}
.song-plays, .song-duration {
width: auto;
margin-right: 1rem;
}
}
</style>
<?php include '../includes/footer.php'; ?>
3. js/player.js (Audio Player)
/**
* Music Player JavaScript
*/
class MusicPlayer {
constructor() {
this.audio = new Audio();
this.currentSong = null;
this.queue = [];
this.currentIndex = 0;
this.isPlaying = false;
this.volume = 0.8;
this.shuffle = false;
this.repeat = 'none'; // none, one, all
this.init();
}
init() {
// Load saved volume
const savedVolume = localStorage.getItem('playerVolume');
if (savedVolume !== null) {
this.volume = parseFloat(savedVolume);
this.audio.volume = this.volume;
}
// Load saved shuffle/repeat
this.shuffle = localStorage.getItem('playerShuffle') === 'true';
this.repeat = localStorage.getItem('playerRepeat') || 'none';
// Event listeners
this.audio.addEventListener('timeupdate', () => this.onTimeUpdate());
this.audio.addEventListener('ended', () => this.onEnded());
this.audio.addEventListener('loadedmetadata', () => this.onLoaded());
this.audio.addEventListener('error', (e) => this.onError(e));
// Keyboard shortcuts
document.addEventListener('keydown', (e) => this.handleKeyPress(e));
}
loadSong(song) {
this.currentSong = song;
this.audio.src = 'stream.php?song_id=' + song.id;
this.audio.load();
// Update UI
this.updatePlayerUI();
// Add to history
this.addToHistory(song.id);
}
loadPlaylist(songs, playlist) {
this.queue = songs;
this.currentIndex = 0;
this.loadSong(songs[0]);
// Update playlist UI
this.updatePlaylistUI(playlist);
}
play() {
this.audio.play();
this.isPlaying = true;
this.updatePlayButton();
}
pause() {
this.audio.pause();
this.isPlaying = false;
this.updatePlayButton();
}
togglePlay() {
if (this.isPlaying) {
this.pause();
} else {
this.play();
}
}
next() {
if (this.queue.length === 0) return;
if (this.repeat === 'one') {
// Repeat current song
this.audio.currentTime = 0;
this.play();
return;
}
let nextIndex = this.currentIndex + 1;
if (this.shuffle) {
// Get random index
nextIndex = Math.floor(Math.random() * this.queue.length);
while (nextIndex === this.currentIndex && this.queue.length > 1) {
nextIndex = Math.floor(Math.random() * this.queue.length);
}
}
if (nextIndex >= this.queue.length) {
if (this.repeat === 'all') {
nextIndex = 0;
} else {
this.pause();
this.currentIndex = 0;
return;
}
}
this.currentIndex = nextIndex;
this.loadSong(this.queue[this.currentIndex]);
this.play();
}
previous() {
if (this.queue.length === 0) return;
// If song has played for more than 3 seconds, restart instead of going to previous
if (this.audio.currentTime > 3) {
this.audio.currentTime = 0;
this.play();
return;
}
let prevIndex = this.currentIndex - 1;
if (this.shuffle) {
prevIndex = Math.floor(Math.random() * this.queue.length);
}
if (prevIndex < 0) {
if (this.repeat === 'all') {
prevIndex = this.queue.length - 1;
} else {
this.audio.currentTime = 0;
this.play();
return;
}
}
this.currentIndex = prevIndex;
this.loadSong(this.queue[this.currentIndex]);
this.play();
}
seek(percentage) {
const time = (percentage / 100) * this.audio.duration;
this.audio.currentTime = time;
}
setVolume(value) {
this.volume = value / 100;
this.audio.volume = this.volume;
localStorage.setItem('playerVolume', this.volume);
}
toggleShuffle() {
this.shuffle = !this.shuffle;
localStorage.setItem('playerShuffle', this.shuffle);
this.updateShuffleButton();
}
toggleRepeat() {
const modes = ['none', 'one', 'all'];
const currentIndex = modes.indexOf(this.repeat);
const nextIndex = (currentIndex + 1) % modes.length;
this.repeat = modes[nextIndex];
localStorage.setItem('playerRepeat', this.repeat);
this.updateRepeatButton();
}
addToQueue(song) {
this.queue.push(song);
this.updateQueueUI();
}
removeFromQueue(index) {
if (index === this.currentIndex) {
// Can't remove currently playing song
return;
}
this.queue.splice(index, 1);
if (index < this.currentIndex) {
this.currentIndex--;
}
this.updateQueueUI();
}
clearQueue() {
this.queue = [];
this.currentIndex = 0;
this.pause();
this.hidePlayer();
this.updateQueueUI();
}
// Event handlers
onTimeUpdate() {
if (!this.audio.duration) return;
const currentTime = this.formatTime(this.audio.currentTime);
const duration = this.formatTime(this.audio.duration);
const progress = (this.audio.currentTime / this.audio.duration) * 100;
this.updateProgress(currentTime, duration, progress);
}
onEnded() {
// Record play count
this.recordPlay(this.currentSong.id);
// Play next
this.next();
}
onLoaded() {
this.updateDuration(this.formatTime(this.audio.duration));
}
onError(e) {
console.error('Player error:', e);
this.showError('Failed to play song');
}
// UI Updates
updatePlayerUI() {
if (!this.currentSong) return;
document.getElementById('current-song-title').textContent = this.currentSong.title;
document.getElementById('current-song-artist').textContent = this.currentSong.artist;
document.getElementById('current-song-image').src = this.currentSong.cover || '/blog-website/assets/default-cover.jpg';
}
updatePlayButton() {
const playBtn = document.getElementById('play-btn');
if (playBtn) {
playBtn.textContent = this.isPlaying ? '⏸' : '▶';
}
}
updateProgress(current, duration, progress) {
const currentEl = document.getElementById('current-time');
const durationEl = document.getElementById('total-time');
const progressEl = document.getElementById('progress-bar');
if (currentEl) currentEl.textContent = current;
if (durationEl) durationEl.textContent = duration;
if (progressEl) progressEl.style.width = progress + '%';
}
updateDuration(duration) {
const durationEl = document.getElementById('total-time');
if (durationEl) durationEl.textContent = duration;
}
updateShuffleButton() {
const btn = document.getElementById('shuffle-btn');
if (btn) {
btn.classList.toggle('active', this.shuffle);
}
}
updateRepeatButton() {
const btn = document.getElementById('repeat-btn');
if (btn) {
btn.classList.toggle('active', this.repeat !== 'none');
btn.textContent = this.repeat === 'one' ? '🔂' : '🔁';
}
}
updateQueueUI() {
// Implement queue UI update
}
updatePlaylistUI(playlist) {
// Implement playlist UI update
}
showPlayer() {
document.getElementById('player-bar').style.display = 'block';
}
hidePlayer() {
document.getElementById('player-bar').style.display = 'none';
}
// Helper functions
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return mins + ':' + (secs < 10 ? '0' : '') + secs;
}
addToHistory(songId) {
fetch('ajax/add-to-history.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'song_id=' + songId
});
}
recordPlay(songId) {
fetch('ajax/record-play.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'song_id=' + songId
});
}
handleKeyPress(e) {
// Space bar
if (e.code === 'Space' && !e.target.matches('input, textarea')) {
e.preventDefault();
this.togglePlay();
}
// Arrow keys
if (e.code === 'ArrowRight' && e.ctrlKey) {
this.next();
}
if (e.code === 'ArrowLeft' && e.ctrlKey) {
this.previous();
}
}
showError(message) {
// Implement error toast
console.error(message);
}
}
// Initialize player
const player = new MusicPlayer();
// Global functions for HTML buttons
function playSong(songId) {
fetch('ajax/get-song.php?song_id=' + songId)
.then(response => response.json())
.then(song => {
player.loadSong(song);
player.showPlayer();
player.play();
});
}
function playPlaylist(playlistId) {
fetch('ajax/get-playlist-songs.php?playlist_id=' + playlistId)
.then(response => response.json())
.then(data => {
player.loadPlaylist(data.songs, data.playlist);
player.showPlayer();
player.play();
});
}
function togglePlay() {
player.togglePlay();
}
function nextSong() {
player.next();
}
function prevSong() {
player.previous();
}
function seek(event) {
const bar = event.currentTarget;
const rect = bar.getBoundingClientRect();
const x = event.clientX - rect.left;
const percentage = (x / rect.width) * 100;
player.seek(percentage);
}
function setVolume(value) {
player.setVolume(value);
}
function toggleShuffle() {
player.toggleShuffle();
}
function toggleRepeat() {
player.toggleRepeat();
}
4. includes/streaming.php (Audio Streaming Handler)
<?php
/**
* Audio Streaming Handler
* Handles efficient streaming of audio files with range support
*/
require_once 'music-config.php';
$songId = $_GET['song_id'] ?? 0;
if (!$songId) {
http_response_code(400);
exit('Song ID required');
}
// Get song details
$pdo = getDB();
$stmt = $pdo->prepare("
SELECT s.*, u.subscription_type
FROM songs s
JOIN users u ON s.artist_id = u.id
WHERE s.id = ? AND s.status = 'approved'
");
$stmt->execute([$songId]);
$song = $stmt->fetch();
if (!$song) {
http_response_code(404);
exit('Song not found');
}
// Check if user has access
$user = getCurrentUser();
if (!$user && !FREE_TIER_ENABLED) {
http_response_code(403);
exit('Please login to listen');
}
$filePath = SONG_PATH . $song['file_path'];
if (!file_exists($filePath)) {
http_response_code(404);
exit('File not found');
}
// Get file info
$fileSize = filesize($filePath);
$fileName = basename($filePath);
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
// Set content type based on file extension
$contentTypes = [
'mp3' => 'audio/mpeg',
'm4a' => 'audio/mp4',
'wav' => 'audio/wav',
'flac' => 'audio/flac',
'aac' => 'audio/aac'
];
$contentType = $contentTypes[$fileExtension] ?? 'application/octet-stream';
// Handle range requests (for seeking)
if (isset($_SERVER['HTTP_RANGE'])) {
$range = $_SERVER['HTTP_RANGE'];
$range = str_replace('bytes=', '', $range);
$range = explode('-', $range);
$start = intval($range[0]);
$end = isset($range[1]) && $range[1] ? intval($range[1]) : $fileSize - 1;
if ($start > $end || $start < 0 || $end >= $fileSize) {
http_response_code(416);
header('Content-Range: bytes */' . $fileSize);
exit;
}
$length = $end - $start + 1;
http_response_code(206);
header('Content-Range: bytes ' . $start . '-' . $end . '/' . $fileSize);
} else {
$start = 0;
$end = $fileSize - 1;
$length = $fileSize;
}
// Set headers
header('Content-Type: ' . $contentType);
header('Content-Length: ' . $length);
header('Accept-Ranges: bytes');
header('Cache-Control: public, max-age=86400');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 86400) . ' GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($filePath)) . ' GMT');
// Open file and output range
$fp = fopen($filePath, 'rb');
fseek($fp, $start);
$chunkSize = STREAM_CHUNK_SIZE;
$bytesSent = 0;
while (!feof($fp) && $bytesSent < $length && connection_status() == 0) {
$remaining = $length - $bytesSent;
$readSize = min($chunkSize, $remaining);
echo fread($fp, $readSize);
flush();
$bytesSent += $readSize;
}
fclose($fp);
// Record play (async - don't wait for it)
if (!isset($_GET['preview'])) {
ignore_user_abort(true);
$pdo = getDB();
// Update play count
$pdo->prepare("UPDATE songs SET play_count = play_count + 1 WHERE id = ?")->execute([$songId]);
$pdo->prepare("UPDATE artists SET total_plays = total_plays + 1 WHERE id = ?")->execute([$song['artist_id']]);
// Add to listening history if user is logged in
if ($user) {
$stmt = $pdo->prepare("
INSERT INTO listening_history (user_id, song_id, duration_played, completed, source, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?)
");
$durationPlayed = isset($_GET['duration']) ? (int)$_GET['duration'] : 0;
$completed = isset($_GET['completed']) ? (int)$_GET['completed'] : 0;
$source = $_GET['source'] ?? 'stream';
$stmt->execute([
$user['id'],
$songId,
$durationPlayed,
$completed,
$source,
$_SERVER['REMOTE_ADDR'],
$_SERVER['HTTP_USER_AGENT'] ?? null
]);
}
}
🚀 How to Use This Project Step by Step
Step 1: Environment Setup
- Ensure PHP 7.4+ with required extensions
- Install Composer
- Enable Apache mod_rewrite
Step 2: Database Setup
- Create database:
music_db - Import
database/music.sql - Update
.envwith database credentials
Step 3: Directory Permissions
chmod -R 777 uploads/ chmod -R 777 uploads/songs/ chmod -R 777 uploads/covers/ chmod -R 777 uploads/avatars/ chmod -R 777 uploads/temp/
Step 4: Install Dependencies
composer install
Step 5: Configure .env
Update all settings in .env file:
- Database credentials
- Platform settings
- Payment gateway keys (if using premium)
Step 6: Test Streaming
- Navigate to
/music/index.php - Try playing a sample song
- Check seek functionality
- Test playlist creation
Step 7: Configure Cron Jobs (Optional)
For royalty calculations and analytics:
# Calculate royalties daily at 2 AM 0 2 * * * php /path/to/blog-website/music/cron/calculate-royalties.php # Update daily stats hourly 0 * * * * php /path/to/blog-website/music/cron/update-stats.php
🔒 Security Features
Audio Security:
- ✅ Direct file access blocked
- ✅ Streaming only through PHP handler
- ✅ Range request support
- ✅ Rate limiting for downloads
Access Control:
- ✅ Premium content restrictions
- ✅ Age verification for explicit content
- ✅ Artist verification system
- ✅ Admin approval for uploads
DRM Features:
- ✅ Streaming only (no direct download for free users)
- ✅ Watermarking for previews
- ✅ Session-based authentication
📊 Analytics Features
Tracked Metrics:
- Total Plays: Per song, artist, album
- Unique Listeners: Daily/Monthly active users
- Listening Time: Total streaming minutes
- User Engagement: Skips, repeats, completion rate
- Geographic Data: Listener locations
- Device Stats: Mobile vs Desktop
Artist Dashboard:
- Real-time play counts
- Listener demographics
- Revenue projections
- Popular tracks ranking
🚀 Future Enhancements
- Advanced Features:
- Collaborative playlists
- Social sharing
- Lyrics synchronization
- Music videos
- AI Features:
- Smart recommendations
- Mood-based playlists
- Auto-generated radio
- Similar artist discovery
- Social Features:
- Friend activity
- Shared playlists
- Comments on songs
- Artist following
- Premium Features:
- Offline downloads
- Higher quality audio
- Ad-free experience
- Exclusive content
- Artist Tools:
- Advanced analytics
- Fan engagement
- Tour dates
- Merchandise integration
📝 Conclusion
This Music Streaming App provides a complete platform for streaming music, creating playlists, and discovering new artists. With features like user authentication, playlist management, audio streaming with range support, and administrative controls, it's perfect for building your own music streaming service. The system is built with performance in mind, using efficient streaming techniques and caching for popular content.