Building TheRadio.Ai
Introduction
Welcome to TheRadio.Ai, a pioneering platform designed to transform how music enthusiasts create and manage their own radio stations. Our vision is to empower users to curate personalized stations using songs they’ve purchased through the application, managed by an AI DJ. This blog post will provide an in-depth look at our project, its features, and the journey towards our launch goals.
Project Overview
Vision and Goals
TheRadio.Ai aims to democratize radio station creation by allowing anyone to become a DJ. Users can create stations with their purchased songs, managed by an AI DJ that operates based on pre-engineered algorithms and user instructions. Our goal is to launch with at least five stations, each featuring over 100 songs, ensuring a diverse and rich listening experience.
Key Features
Personalized Radio Station Creation
Users can create and manage their own radio stations using songs they've purchased through the platform. Once a station has at least 100 songs, it can be shared publicly. Visitors can tune in and listen to the music being played in real-time.
From Humble Beginnings... Watch the very first moments of the creation of theRadio.Ai.
Follow this blog post to watch the application come to live before your very eyes!
AI DJ Management
The AI DJ operates based on algorithms and user instructions, managing the station’s operations seamlessly. This includes song selection, ensuring variety, and maintaining a dynamic and engaging playlist.
Real-time Interaction and Voting
Listeners can interact with the station by voting for songs they want to hear next. The "Request Next" and "Buy" options allow users to influence the playlist dynamically.
Music Purchase Options
Songs can be purchased individually, by album, or within crates, which are collections of albums curated by artists or labels. This flexibility supports varied user preferences and enhances the music discovery experience.
Technical Implementation
Codebase Structure
The project's codebase is meticulously organized to ensure efficient development and maintenance. Here’s a look at the file structure:
.
├── ActivatingAndTestingServer.md
├── favicon.ico
├── Handling shout outs, push stream promotions.txt
├── package-lock.json
├── package.json
├── README.md
├── server.js
├── tempIndex.html
└── node_modules/
Front-end Development
Our front-end is built using HTML, CSS, and JavaScript, with Bootstrap for responsive design. Key components include:
index.html: The main HTML file with structured content.
styles.css: Custom styles for a vibrant and user-friendly interface.
app.js: JavaScript functionality to handle media playback, song voting, and dynamic updates.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Lake County Underground Radio - Discover and support underground artists from Lake County.">
<title id="dynamic-title">Lake County Underground Radio</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="css/styles.css">
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<div class="container py-4 custom-container">
<header class="header">
<img src="TheRadioAiLogo.png" alt="Lake County Underground Radio Logo" class="logo">
<div class="header-content">
<h1>Lake County Underground Radio</h1>
</div>
</header>
<div class="preset-button-container">
<div class="preset-button" id="preset1">Preset 1</div>
<div class="preset-button" id="preset2">Preset 2</div>
<div class="preset-button" id="preset3">Preset 3</div>
<div class="preset-button" id="preset4">Preset 4</div>
<div class="preset-button" id="preset5">Preset 5</div>
</div>
<div class="media-player-wrapper d-flex justify-content-center">
<div id="media-player-container">
<video id="video-player" controls autoplay controlsList="nodownload">
Your browser does not support the video tag.
</video>
</div>
</div>
<div class="song-details d-flex align-items-center justify-content-center">
<h3 id="current-song" class="mx-3">Loading...</h3>
</div>
<div class="button-row d-flex justify-content-center flex-wrap">
<button class="btn-custom btn-primary me-3" id="open-chat">Live Chat</button>
<button class="btn-custom btn-primary me-3">Save</button>
<button class="btn-custom btn-success ms-3">Buy</button>
<button class="btn-custom btn-primary ms-3" id="open-chart">Songs on Station</button>
</div>
<div id="chat-overlay" class="overlay">
<div class="overlay-content">
<button id="close-chat" class="close-btn">×</button>
<h2>Live Chat</h2>
<div id="chat-messages" class="chat-messages"></div>
<input type="text" id="chat-input" placeholder="Type your message...">
<button id="send-chat" class="btn-custom btn-primary">Send</button>
</div>
</div>
<div id="chart-overlay" class="overlay">
<div class="overlay-content">
<button id="close-chart" class="close-btn">×</button>
<h2>Songs on Station</h2>
<div class="d-flex justify-content-between mb-3">
<div class="d-flex">
<div class="me-3">
<input type="text" id="search-input" class="form-control" placeholder="Search by name, genre, BPM, or artist">
</div>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="columnsDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Columns
</button>
<ul class="dropdown-menu" aria-labelledby="columnsDropdown">
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="points" checked> Points</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="title" checked> Song Title</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="artist" checked> Artist Name</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="preview" checked> <i class="fas fa-play"></i> Preview</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="save" checked> <i class="fas fa-save"></i> Save</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="buy" checked> <i class="fas fa-shopping-cart"></i> Buy</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="request" checked> <i class="fas fa-vote-yea"></i> Request Next</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="label"> Label</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="location"> Location</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="bpm"> BPM</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="date"> Date</a></li>
<li><a class="dropdown-item"><input type="checkbox" class="column-toggle" data-column="genre"> Genre</a></li>
</ul>
</div>
</div>
</div>
<div class="table-responsive" style="max-height: 60vh; overflow-y: auto;">
<table class="table table-striped" id="songs-table">
<thead>
<tr>
<th data-column="points">Points <span class="sort-arrow"></span></th>
<th data-column="title">Song Title <span class="sort-arrow"></span></th>
<th data-column="artist">Artist Name <span class="sort-arrow"></span></th>
<th data-column="preview"><i class="fas fa-play"></i> Preview</th>
<th data-column="save"><i class="fas fa-save"></i> Save</th>
<th data-column="buy"><i class="fas fa-shopping-cart"></i> Buy</th>
<th data-column="request"><i class="fas fa-vote-yea"></i> Request Next</th>
<th data-column="label" class="d-none">Label</th>
<th data-column="location" class="d-none">Location</th>
<th data-column="bpm" class="d-none">BPM</th>
<th data-column="date" class="d-none">Date</th>
<th data-column="genre" class="d-none">Genre</th>
</tr>
</thead>
<tbody>
<!-- Dynamically filled rows will appear here -->
</tbody>
</table>
<div class="text-end mt-3">
<button id="see-all-btn" class="btn btn-secondary">... See All</button>
</div>
</div>
</div>
</div>
</div>
<button id="back-to-top" class="back-to-top"><i class="fas fa-arrow-up"></i></button>
<div class="bottom-nav">
<a href="#" class="bottom-nav-link">
<i class="fas fa-play"></i>
<span class="bottom-nav-text">Now Playing</span>
</a>
<a href="#" class="bottom-nav-link">
<i class="fas fa-music"></i>
<span class="bottom-nav-text">Music</span>
</a>
<a href="#" class="bottom-nav-link">
<i class="fas fa-users"></i>
<span class="bottom-nav-text">Artists</span>
</a>
<a href="#" class="bottom-nav-link">
<i class="fas fa-broadcast-tower"></i>
<span class="bottom-nav-text">Stations</span>
</a>
<a href="#" class="bottom-nav-link">
<i class="fas fa-rss"></i>
<span class="bottom-nav-text">Feed</span>
</a>
<a href="#" class="bottom-nav-link">
<i class="fas fa-user"></i>
<span class="bottom-nav-text">Me</span>
</a>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.1.3/js/bootstrap.bundle.min.js"></script>
<script src="scripts/app.js"></script>
</body>
</html>
app.js
document.addEventListener("DOMContentLoaded", () => {
let mediaFiles = [];
const mediaPlayer = document.getElementById('video-player');
const currentSongDisplay = document.getElementById('current-song');
const tableBody = document.querySelector('#songs-table tbody');
const presetButtons = document.querySelectorAll('.preset-button');
const dynamicTitle = document.getElementById('dynamic-title');
const backToTopButton = document.getElementById('back-to-top');
const searchInput = document.getElementById('search-input');
const columnsDropdown = document.querySelectorAll('.column-toggle');
const seeAllBtn = document.getElementById('see-all-btn');
const tableHeaders = document.querySelectorAll('#songs-table th');
let votedThisSession = false;
let stationTitle = "Lake County Underground Radio";
let lastPlayedSongs = [];
const playPermissionPeriod = 5;
let isShowingAll = false;
let sortColumn = 'points';
let sortDirection = 'desc';
function fetchMediaFiles() {
fetch('http://localhost:3000/media')
.then(response => response.json())
.then(data => {
mediaFiles = data;
console.log("Media files fetched:", mediaFiles);
updateSongsTable();
if (mediaFiles.length > 0) {
playNextSong();
}
})
.catch(error => console.error('Error fetching media files:', error));
}
function updateSongsTable() {
let filteredFiles = mediaFiles.filter(file => {
const searchTerm = searchInput.value.toLowerCase();
return (
file.title.toLowerCase().includes(searchTerm) ||
file.artist.toLowerCase().includes(searchTerm) ||
file.genre.toLowerCase().includes(searchTerm) ||
file.bpm.toString().includes(searchTerm)
);
});
filteredFiles.sort((a, b) => {
if (sortDirection === 'asc') {
return a[sortColumn] > b[sortColumn] ? 1 : -1;
} else {
return a[sortColumn] < b[sortColumn] ? 1 : -1;
}
});
if (!isShowingAll && filteredFiles.length > 40) {
filteredFiles = filteredFiles.slice(0, 40);
seeAllBtn.style.display = 'block';
} else {
seeAllBtn.style.display = 'none';
}
tableBody.innerHTML = '';
filteredFiles.forEach((file, index) => {
const row = tableBody.insertRow();
fillRowWithData(row, file);
});
}
function fillRowWithData(row, songInfo) {
row.insertCell(0).textContent = songInfo.points || 100;
row.insertCell(1).textContent = songInfo.title;
row.insertCell(2).textContent = songInfo.artist;
const previewButton = document.createElement('button');
previewButton.className = 'btn-custom btn-secondary';
previewButton.innerHTML = '<i class="fas fa-play"></i><span>Preview</span>';
row.insertCell(3).appendChild(previewButton);
const saveButton = document.createElement('button');
saveButton.className = 'btn-custom btn-primary';
saveButton.innerHTML = '<i class="fas fa-save"></i><span>Save</span>';
saveButton.addEventListener('click', () => handleSave(songInfo));
row.insertCell(4).appendChild(saveButton);
const buyButton = document.createElement('button');
buyButton.className = 'btn-custom btn-success';
buyButton.innerHTML = '<i class="fas fa-shopping-cart"></i><span>Buy</span>';
buyButton.addEventListener('click', () => handleBuy(songInfo));
row.insertCell(5).appendChild(buyButton);
const requestBtn = document.createElement('button');
requestBtn.className = 'btn-custom btn-info';
requestBtn.innerHTML = votedThisSession ? '<span>Voted</span>' : '<i class="fas fa-vote-yea"></i><span>Request Next</span>';
requestBtn.disabled = votedThisSession;
requestBtn.addEventListener('click', () => handleVote(songInfo, requestBtn));
row.insertCell(6).appendChild(requestBtn);
const labelCell = row.insertCell(7);
labelCell.textContent = 'Independent';
labelCell.classList.add('d-none');
const locationCell = row.insertCell(8);
locationCell.textContent = songInfo.location || 'Unknown';
locationCell.classList.add('d-none');
const bpmCell = row.insertCell(9);
bpmCell.textContent = songInfo.bpm || 'N/A';
bpmCell.classList.add('d-none');
const dateCell = row.insertCell(10);
dateCell.textContent = songInfo.date || 'Unknown';
dateCell.classList.add('d-none');
const genreCell = row.insertCell(11);
genreCell.textContent = songInfo.genre || 'Unknown';
genreCell.classList.add('d-none');
}
function handleSave(song) {
console.log("Saving song:", song);
fetch('http://localhost:3000/updatePoints', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: song.file, points: 10 })
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Save error:', data.error);
alert(data.error);
} else {
song.points = data.points;
console.log("Updated song after save:", song);
updateSongsTable();
}
})
.catch(error => {
console.error('Error saving:', error);
alert('Failed to save. Try again.');
});
}
function handleBuy(song) {
console.log("Buying song:", song);
fetch('http://localhost:3000/updatePoints', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: song.file, points: 100 })
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Buy error:', data.error);
alert(data.error);
} else {
song.points = data.points;
console.log("Updated song after buy:", song);
updateSongsTable();
}
})
.catch(error => {
console.error('Error buying:', error);
alert('Failed to buy. Try again.');
});
}
function handleVote(songInfo, button) {
if (!votedThisSession) {
console.log("Voting for song:", songInfo);
fetch('http://localhost:3000/vote', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: songInfo.file, points: 25 })
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Vote error:', data.error);
alert(data.error);
} else {
votedThisSession = true;
button.innerHTML = '<span>Voted</span>';
disableAllVoteButtons();
alert('Vote registered for ' + songInfo.title);
songInfo.points = data.points;
console.log("Updated song after vote:", songInfo);
updateSongsTable();
}
})
.catch(error => {
console.error('Error voting:', error);
alert('Failed to register vote. Try again.');
});
}
}
function disableAllVoteButtons() {
const voteButtons = document.querySelectorAll('#songs-table .btn-custom.btn-info');
voteButtons.forEach(button => {
button.disabled = true;
});
}
function resetVoteButtons() {
const voteButtons = document.querySelectorAll('#songs-table .btn-custom.btn-info');
voteButtons.forEach(button => {
button.disabled = false;
button.innerHTML = '<i class="fas fa-vote-yea"></i><span>Request Next</span>';
});
votedThisSession = false;
}
function weightedRandomSelection() {
let totalPoints = mediaFiles.reduce((sum, song) => sum + song.points, 0);
let randomPoint = Math.random() * totalPoints;
let selectedMedia;
for (let song of mediaFiles) {
if (randomPoint < song.points && !lastPlayedSongs.includes(mediaFiles.indexOf(song))) {
selectedMedia = song;
break;
}
randomPoint -= song.points;
}
if (!selectedMedia) {
lastPlayedSongs.shift();
selectedMedia = mediaFiles.find(song => !lastPlayedSongs.includes(mediaFiles.indexOf(song)));
}
return selectedMedia;
}
function playNextSong() {
let selectedMedia = weightedRandomSelection();
// Add 50 points to the song for being played
fetch('http://localhost:3000/updatePoints', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ file: selectedMedia.file, points: 50 })
})
.then(response => response.json())
.then(data => {
if (data.error) {
console.error('Error updating points for play:', data.error);
} else {
selectedMedia.points = data.points;
console.log("Updated song after play:", selectedMedia);
updateSongsTable();
}
})
.catch(error => {
console.error('Error updating points for play:', error);
});
if (lastPlayedSongs.length >= playPermissionPeriod) {
lastPlayedSongs.shift();
}
lastPlayedSongs.push(mediaFiles.indexOf(selectedMedia));
mediaPlayer.src = `http://localhost:3000/media/${decodeURIComponent(selectedMedia.file)}`;
mediaPlayer.load();
mediaPlayer.play();
currentSongDisplay.textContent = `${selectedMedia.artist} - ${selectedMedia.title}`;
updateTabTitle(selectedMedia);
votedThisSession = false; // Reset the vote session
resetVoteButtons(); // Reset vote buttons state
updateActivePresetButtons(selectedMedia);
}
function updateTabTitle(songInfo) {
dynamicTitle.textContent = `Lake County Underground Radio: ${songInfo.artist} - ${songInfo.title}`;
}
function handlePresetClick(event) {
const presetButton = event.currentTarget;
const currentSong = currentSongDisplay.textContent;
presetButtons.forEach(button => {
button.classList.remove('active');
button.textContent = button.id.replace('preset', 'Preset ');
});
presetButton.classList.add('active');
presetButton.innerHTML = `<span class="scrolling-text">${currentSong}</span>`;
updateTabTitle({ title: currentSong.split(' - ')[1], artist: currentSong.split(' - ')[0] });
}
function updateActivePresetButtons(songInfo) {
presetButtons.forEach(button => {
if (button.classList.contains('active')) {
button.innerHTML = `<span class="scrolling-text">${stationTitle}: ${songInfo.title} - ${songInfo.artist}</span>`;
}
});
}
function handleColumnToggle(event) {
const columnClass = event.target.dataset.column;
const columnCells = document.querySelectorAll(`[data-column="${columnClass}"], .${columnClass}-cell`);
columnCells.forEach(cell => {
cell.classList.toggle('d-none', !event.target.checked);
});
}
function handleSort(event) {
const column = event.currentTarget.dataset.column;
if (sortColumn === column) {
sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
} else {
sortColumn = column;
sortDirection = 'asc';
}
updateSortArrows();
updateSongsTable();
}
function updateSortArrows() {
tableHeaders.forEach(header => {
const column = header.dataset.column;
const arrow = header.querySelector('.sort-arrow');
if (column === sortColumn) {
header.classList.add(sortDirection === 'asc' ? 'sort-asc' : 'sort-desc');
arrow.style.display = 'inline-block';
} else {
header.classList.remove('sort-asc', 'sort-desc');
arrow.style.display = 'none';
}
});
}
presetButtons.forEach(button => {
button.addEventListener('click', handlePresetClick);
});
mediaPlayer.onended = playNextSong;
window.addEventListener('scroll', () => {
if (window.pageYOffset > 100) {
backToTopButton.style.display = 'flex';
} else {
backToTopButton.style.display = 'none';
}
});
backToTopButton.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
searchInput.addEventListener('input', updateSongsTable);
columnsDropdown.forEach(toggle => {
toggle.addEventListener('change', handleColumnToggle);
});
tableHeaders.forEach(header => {
header.addEventListener('click', handleSort);
});
seeAllBtn.addEventListener('click', () => {
isShowingAll = !isShowingAll;
updateSongsTable();
});
fetchMediaFiles();
const chatOverlay = document.getElementById('chat-overlay');
const chartOverlay = document.getElementById('chart-overlay');
const openChatBtn = document.getElementById('open-chat');
const openChartBtn = document.getElementById('open-chart');
const closeChatBtn = document.getElementById('close-chat');
const closeChartBtn = document.getElementById('close-chart');
openChatBtn.addEventListener('click', () => {
chatOverlay.style.display = 'flex';
});
closeChatBtn.addEventListener('click', () => {
chatOverlay.style.display = 'none';
});
openChartBtn.addEventListener('click', () => {
chartOverlay.style.display = 'flex';
});
closeChartBtn.addEventListener('click', () => {
chartOverlay.style.display = 'none';
});
});
Back-end Development
The back-end is powered by Node.js and Express.js, ensuring robust server-side functionality. Key components include:
server.js: Manages server operations, including session handling, media file serving, and interaction endpoints.
Database Management: We use JSON files for storing song data and user interactions temporarily, which can be scaled to a full-fledged database as we grow.
server.js
const express = require('express');
const session = require('express-session');
const bodyParser = require('body-parser');
const path = require('path');
const fs = require('fs');
const cors = require('cors');
const WebSocket = require('ws');
const app = express();
const PORT = 3000;
const wss = new WebSocket.Server({ port: 8080 });
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('public'));
app.use('/media', express.static(path.join(__dirname, 'src', 'media')));
app.get('/favicon.ico', (req, res) => res.status(204));
app.use(session({
secret: 'your-unique-secret-key',
saveUninitialized: true,
resave: false,
cookie: { secure: false, httpOnly: true }
}));
let songsData = [];
const mediaDir = path.join(__dirname, 'src', 'media');
async function loadSongsData() {
try {
const files = await fs.promises.readdir(mediaDir);
songsData = files.map(file => {
const parts = file.split(' - ');
const artist = parts.shift();
const title = parts.join(' - ').replace(/\.(mp3|mp4)$/, '');
return {
file: encodeURIComponent(file),
artist,
title,
votes: 0,
points: 100,
bonusPoints: 0,
lastUpdated: Date.now(),
addedAt: Date.now()
};
});
console.log('Song data loaded:', songsData);
} catch (error) {
console.error('Error loading song data:', error);
}
}
app.post('/vote', (req, res) => {
const { file, points } = req.body;
console.log(`Vote request received for file: ${file} with points: ${points}`);
const song = songsData.find(song => song.file === file);
if (!song) {
return res.status(404).json({ error: 'Song not found' });
}
if (req.session.voted) {
return res.status(409).json({ error: 'Already voted in this session' });
}
song.votes++;
song.points += points;
song.lastUpdated = Date.now();
req.session.voted = true;
req.session.save(err => {
if (err) {
console.error('Error saving session:', err);
return res.status(500).json({ error: 'Failed to save session' });
}
console.log(`Vote registered for ${song.title}. Points: ${song.points}`);
res.status(200).json({ points: song.points });
});
});
app.post('/updatePoints', (req, res) => {
const { file, points } = req.body;
console.log(`Update points request received for file: ${file} with points: ${points}`);
const song = songsData.find(song => song.file === file);
if (!song) {
return res.status(404).json({ error: 'Song not found' });
}
song.points += points;
song.lastUpdated = Date.now();
req.session.save(err => {
if (err) {
console.error('Error saving session:', err);
return res.status(500).json({ error: 'Failed to save session' });
}
console.log(`Points updated for ${song.title}. Points: ${song.points}`);
res.status(200).json({ points: song.points });
});
});
app.post('/reset', (req, res) => {
songsData.forEach(song => {
song.votes = 0;
song.points = 100;
song.bonusPoints = 0;
song.lastUpdated = Date.now();
});
req.session.voted = false;
req.session.save(err => {
if (err) {
return res.status(500).send('Failed to reset session');
}
console.log('Session and song points reset.');
res.send('Session and votes reset');
});
});
app.get('/media', (req, res) => {
res.status(200).json(songsData.map(song => ({
file: song.file,
title: song.title,
artist: song.artist,
points: song.points,
bonusPoints: song.bonusPoints,
location: 'Unknown',
bpm: 120,
date: '2024-01-01',
genre: 'Unknown'
})));
});
function deprecatePoints() {
const now = Date.now();
songsData.forEach(song => {
const hoursSinceAdded = (now - song.addedAt) / (1000 * 60 * 60);
const daysSinceAdded = Math.floor(hoursSinceAdded / 24);
if (daysSinceAdded > 0) {
const depreciationFactor = Math.pow(0.99, daysSinceAdded);
song.points *= depreciationFactor;
song.points = Math.max(Math.floor(song.points), 0);
song.addedAt += daysSinceAdded * 24 * 60 * 60 * 1000;
console.log(`Depreciated points for ${song.title}. New points: ${song.points}`);
}
});
}
setInterval(deprecatePoints, 24 * 60 * 60 * 1000);
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
loadSongsData();
});
wss.on('connection', ws => {
console.log('Client connected');
ws.on('message', message => {
console.log(`Received: ${message}`);
const chatMessage = JSON.parse(message);
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(chatMessage));
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Standard Operating Procedures
Spotlight Video Creation
To maintain consistency and professionalism, we follow a standardized script for introducing new songs:
Hello, everyone! Thanks for tuning in to The Radio dot a-eye. Today, we're excited to spotlight a brand-new track called "<Song Title>" by <Artist Name> from <Label/Production>. This exclusive preview showcases the incredible work being done at The Radio dot a-eye.
Livestream Event Setup
Livestream titles and descriptions are crafted to be engaging and informative, ensuring viewers know what to expect and how to join:
Title: TheRadio.Ai: Exclusive Spotlight on "<Song Title>" by <Artist Name>!
Description: Join us LIVE for an exclusive spotlight on "<Song Title>" by <Artist Name> from <Label/Production>! Tune in LIVE (start from the beginning): <Live Link>
Social Media Engagement
We create concise and engaging blurbs for social media platforms to attract viewers to our livestreams:
Twitter: 🌟 Don't miss our exclusive spotlight on "<Song Title>" by <Artist Name>! Tune in LIVE (start from the beginning): <Live Link> 🎶 #TheRadioAi #NewMusic
Facebook: 🚀 Join us LIVE for a special spotlight on "<Song Title>" by <Artist Name>! Tune in LIVE (start from the beginning): <Live Link> 🎧 #TheRadioAi #ExclusivePreview
Instagram: 🎤 Live Now! Spotlight on "<Song Title>" by <Artist Name>. Tune in LIVE (start from the beginning): <Live Link> 🌐 #TheRadioAi #MusicSpotlight #LiveStream
Enhancements and Iterative Improvements
We continually enhance the platform based on feedback and iterative improvements. Key enhancements include:
Conditional Logic for Phonetic Spelling: Tailoring output for voice scripts and written content.
Mandatory Phrase Checks: Ensuring essential phrases are included in all outputs.
Standardized Output Templates: Maintaining consistency in all communications.
Link Verification and Integration: Correctly integrating and verifying links in outputs.
Moving to the Cloud
To enhance scalability, reliability, and performance, TheRadio.Ai will transition to a cloud infrastructure utilizing AWS services. Here’s a high-level plan for this migration:
Infrastructure as Code (IaC)
We'll use Terraform to define and manage our cloud infrastructure. This includes provisioning EC2 instances, setting up S3 for storage, and configuring security groups.
Containerization with Docker and Kubernetes
Docker will be used to containerize our application, ensuring consistency across different environments. Kubernetes (EKS) will manage container orchestration, providing auto-scaling and load balancing.
AWS Services Integration
AWS Fargate: To run our containers without managing servers.
AWS Lambda: For serverless functions that handle specific tasks such as data processing.
AWS API Gateway: To create, publish, maintain, monitor, and secure APIs.
AWS S3: For storing media files and backups.
AWS KMS (Key Management Service): To manage encryption keys for securing our data.
Monitoring and Logging
Using CloudWatch for monitoring application performance and logging. Integration with Splunk and Grafana for advanced visualization and alerting.
CI/CD Pipeline
Implementing a continuous integration and continuous deployment pipeline using AWS CodePipeline and CodeBuild to automate the build, test, and deployment process.
Conclusion
TheRadio.Ai is set to revolutionize the way we experience and manage radio stations. With a blend of user creativity and AI management, our platform offers a unique, interactive, and engaging experience for both station creators and listeners. Stay tuned as we continue to develop and refine our platform, bringing innovative music experiences to life.
Implementing TheRadio.Ai: Detailed Technical Guide for Cloud Infrastructure and AI Integration
Introduction
In this blog post, we will dive into the detailed technical aspects of implementing TheRadio.Ai's infrastructure. We'll cover the integration of various AWS services, containerization, AI management, real-time interaction, and the CI/CD pipeline. This guide will serve as a comprehensive reference for our development team and as an insightful read for anyone interested in the technical journey behind TheRadio.Ai.
Project Phase Overview
Goal: To build a robust, scalable, and efficient infrastructure for TheRadio.Ai, leveraging cloud technologies, containerization, AI, and real-time interaction capabilities.
Technology Stack and Skills Required
Cloud Services: AWS
AWS Fargate, ECS, Lambda, API Gateway, EC2, S3, ALB/ELB, ElastiCache, EKS, KMS-Secret Manager, VPCs
Containerization
Docker, Kubernetes (EKS)
Programming Languages
Go, Python, NodeJS, Java
Logging/Monitoring/Alerting
CloudWatch, Splunk, AppDynamics, ElastiCache, Grafana
CI/CD Techniques
AWS CodePipeline, CodeBuild, Jenkins
Additional Cloud Knowledge
GCP, Azure
Networking and Troubleshooting
Detailed Implementation Plan
Infrastructure as Code (IaC) with Terraform
Step 1: Define and Manage Cloud Infrastructure
Use Terraform to write configuration files for AWS resources.
Provision EC2 instances, set up S3 for storage, and configure security groups.
Create and manage VPCs, subnets, and route tables.
Terraform Example:
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "subnet1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
resource "aws_security_group" "allow_web" {
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Containerization with Docker and Kubernetes (EKS)
Step 2: Dockerize the Application
Create Dockerfiles for the front-end and back-end applications.
Build and push Docker images to AWS ECR (Elastic Container Registry).
Dockerfile Example for Node.js Backend:
# Use an official Node.js runtime as a parent image
FROM node:14
# Set the working directory
WORKDIR /usr/src/app
# Copy the package.json and package-lock.json files
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Run the application
CMD ["node", "server.js"]
Step 3: Deploy Containers with Kubernetes (EKS)
Set up an EKS cluster using Terraform.
Deploy Docker containers to EKS using Kubernetes manifests.
Kubernetes Deployment Example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: theradio-backend
spec:
replicas: 3
selector:
matchLabels:
app: theradio-backend
template:
metadata:
labels:
app: theradio-backend
spec:
containers:
- name: theradio-backend
image: <AWS_ECR_IMAGE_URL>
ports:
- containerPort: 3000
AI DJ Management and Real-time Interaction
Step 4: Implement AI DJ Management
Develop the AI DJ using Python with TensorFlow or PyTorch.
Integrate AI logic for song selection based on pre-engineered algorithms and user instructions.
AI DJ Example (Python):
import random
class AIDJ:
def __init__(self, song_data):
self.song_data = song_data
def select_song(self):
# Example algorithm for song selection
total_points = sum(song['points'] for song in self.song_data)
random_point = random.uniform(0, total_points)
current_point = 0
for song in self.song_data:
current_point += song['points']
if current_point >= random_point:
return song
Step 5: Real-time Interaction with WebSockets
Set up a WebSocket server using Node.js to handle live chat and song voting.
WebSocket Server Example (Node.js):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
console.log('Client connected');
ws.on('message', message => {
console.log(`Received: ${message}`);
const chatMessage = JSON.parse(message);
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(chatMessage));
}
});
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
Logging, Monitoring, and Alerting
Step 6: Implement Logging and Monitoring
Use CloudWatch for monitoring application performance and logging.
Integrate with Splunk and Grafana for advanced visualization and alerting.
CloudWatch Configuration Example:
logs:
- name: "/aws/containerinsights/<cluster-name>/application"
retention: 7
- name: "/aws/containerinsights/<cluster-name>/performance"
retention: 7
CI/CD Pipeline
Step 7: Set Up CI/CD Pipeline
Implement a continuous integration and continuous deployment pipeline using AWS CodePipeline and CodeBuild.
Automate the build, test, and deployment process.
AWS CodePipeline Example:
Resources:
MyPipeline:
Type: "AWS::CodePipeline::Pipeline"
Properties:
RoleArn: !GetAtt MyPipelineRole.Arn
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: Source
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
OutputArtifacts:
- Name: SourceArtifact
Configuration:
RepositoryName: !Ref RepositoryName
BranchName: master
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
Configuration:
ProjectName: !Ref CodeBuildProject
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: ECS
Version: 1
InputArtifacts:
- Name: BuildArtifact
Configuration:
ClusterName: !Ref EcsCluster
ServiceName: !Ref EcsService
Additional Cloud Knowledge
Step 8: Leverage Multi-Cloud Expertise
Explore GCP and Azure for potential multi-cloud deployment strategies and redundancy.
Use services like Google Kubernetes Engine (GKE) and Azure Kubernetes Service (AKS) to diversify deployment environments.
Networking and Troubleshooting
Step 9: Enhance Networking Skills
Troubleshoot connectivity issues with tools like ping, traceroute, and AWS VPC Flow Logs.
Ensure secure and efficient network configuration with VPCs, subnets, and security groups.
Network Troubleshooting Example:
# Check connectivity to a remote server
ping example.com
# Trace the route to a remote server
traceroute example.com
# Analyze VPC Flow Logs for network traffic
aws ec2 describe-flow-logs --filter Name=resource-id,Values=<vpc-id>
Conclusion
This detailed technical guide outlines the comprehensive steps to implement TheRadio.Ai's infrastructure. By leveraging cloud technologies, containerization, AI management, real-time interaction, and a robust CI/CD pipeline, we aim to create a scalable, reliable, and engaging platform for personalized radio stations. This guide will serve as a valuable reference for our development team and an insightful resource for anyone following our journey.
Stay tuned for more updates as we continue to develop and refine TheRadio.Ai, bringing innovative music experiences to life.
Summary of Current Progress on TheRadio.Ai Project
Key Points and Objectives
Initial Goals:
Prepare for a DevOps Interview: Focus on various technical skill sets.
Proficiency in Required Technologies: Aim to become proficient in technologies necessary for a DevOps role.
Utilize TheRadio.Ai Project: Leverage this project to build required skills and showcase capabilities.
Interview Preparation:
Focus Areas:
AWS Services: Fargate, ECS, Lambda, API Gateway, EC2, S3, ALB/ELB, ElastiCache, EKS, KMS-Secret Manager, VPCs.
Containerization: Docker and Kubernetes.
Programming Languages: Go, Python, NodeJS, Java.
Logging/Monitoring/Alerting: CloudWatch, Splunk, AppDynamics, ElastiCache, Grafana.
CI/CD Techniques: AWS CodePipeline, CodeBuild, Jenkins.
Additional Cloud Knowledge: GCP, Azure.
Networking Troubleshooting: Tools and techniques for network troubleshooting.
Provided Answers and Explanations: Addressed interview questions related to these topics.
Project Overview:
TheRadio.Ai: A platform for creating and managing radio stations with user-purchased songs. Managed by an AI DJ following user instructions and pre-engineered algorithms.
Launch Goals: At least 5 different stations with 100+ songs each.
Detailed Technical Guide Blog Posts
First Blog Post:
Content:
Project scope, technical stack, implementation strategy.
Covered front-end and back-end development, AI DJ management, real-time interaction, cloud infrastructure migration, logging, monitoring, alerting, and CI/CD pipeline.
Second Blog Post:
Content:
Detailed technical steps and technologies used in the current project phase.
Practical exercises for each key area including IaC with Terraform, containerization with Docker and Kubernetes, AWS services integration, AI management, real-time interaction, logging and monitoring, CI/CD pipeline, networking, troubleshooting, and multi-cloud deployment.
Learning Path for Proficiency
Step-by-Step Educational Path:
Infrastructure as Code (IaC) with Terraform:
Tutorials and exercises for setting up VPCs, subnets, security groups, and EC2 instances.
Containerization with Docker and Kubernetes (EKS):
Creating Dockerfiles, building images, pushing to AWS ECR, deploying to EKS.
AWS Services Integration:
Setting up Fargate tasks, Lambda functions, API Gateway, S3, ElastiCache, and KMS.
AI DJ Management and Real-time Interaction:
Developing an AI DJ with Python, implementing WebSocket communication.
Logging, Monitoring, and Alerting:
Using CloudWatch, Splunk, and Grafana.
CI/CD Pipeline:
Setting up AWS CodePipeline, CodeBuild, and Jenkins pipelines.
Networking and Troubleshooting:
Using ping, traceroute, and VPC Flow Logs.
Multi-Cloud Deployment:
Deploying on GKE and AKS, implementing a multi-cloud strategy.
Next Steps:
Step 1: Infrastructure as Code (IaC) with Terraform.
Sequential Progression: Follow each step to build expertise and implement TheRadio.Ai.
Communication Structures
Step-by-Step Guidance: Detailed instructions and resources for each learning step.
Practical Exercises: Hands-on experience through exercises and examples.
Real-Time Support: Offered real-time responses and clarifications.
Documentation: Comprehensive blog posts documenting technical aspects and learning path.
Condensed Roadmap for TheRadio.Ai (1 Month Completion)
Week 1
Infrastructure as Code (IaC) with Terraform
Duration: 1 week
Containerization with Docker and Kubernetes (EKS)
Duration: 1 week
Start simultaneously with IaC setup.
Week 2
AI DJ Management
Duration: 1 week
Real-time Interaction
Duration: 1 week
Start simultaneously with AI DJ Management.
Week 3
Logging, Monitoring, and Alerting
Duration: 1 week
CI/CD Pipeline
Duration: 1 week
Start simultaneously with logging and monitoring setup.
Enhanced AI Features
Duration: 1 week
Start simultaneously with logging and monitoring setup.
Week 4
Mobile App Development
Duration: 1 week
User Engagement
Duration: 1 week
Start simultaneously with mobile app development.
Monetization Strategies
Duration: 1 week
Start simultaneously with mobile app development.
Performance Optimization
Duration: 1 week
Start simultaneously with mobile app development.
Launch Preparation and Rollout
Duration: 1 week
Start simultaneously with mobile app development.
Instructions for Claude AI:
Instructions for Claude AI:
Please create a Gantt chart for the development of TheRadio.Ai project, with a condensed timeline to complete the entire project in 1 month. Below is the detailed roadmap and timeline:
Week 1
Infrastructure as Code (IaC) with Terraform
Duration: 1 week
Start Date: Day 1
End Date: Day 7
Containerization with Docker and Kubernetes (EKS)
Duration: 1 week
Start Date: Day 1
End Date: Day 7
Runs in parallel with IaC setup.
Week 2
AI DJ Management
Duration: 1 week
Start Date: Day 8
End Date: Day 14
Real-time Interaction
Duration: 1 week
Start Date: Day 8
End Date: Day 14
Runs in parallel with AI DJ Management.
Week 3
Logging, Monitoring, and Alerting
Duration: 1 week
Start Date: Day 15
End Date: Day 21
CI/CD Pipeline
Duration: 1 week
Start Date: Day 15
End Date: Day 21
Runs in parallel with logging and monitoring setup.
Enhanced AI Features
Duration: 1 week
Start Date: Day 15
End Date: Day 21
Runs in parallel with logging and monitoring setup.
Week 4
Mobile App Development
Duration: 1 week
Start Date: Day 22
End Date: Day 28
User Engagement
Duration: 1 week
Start Date: Day 22
End Date: Day 28
Runs in parallel with mobile app development.
Monetization Strategies
Duration: 1 week
Start Date: Day 22
End Date: Day 28
Runs in parallel with mobile app development.
Performance Optimization
Duration: 1 week
Start Date: Day 22
End Date: Day 28
Runs in parallel with mobile app development.
Launch Preparation and Rollout
Duration: 1 week
Start Date: Day 22
End Date: Day 28
Runs in parallel with mobile app development.
Timeline Overview
Total Duration: 4 weeks (1 month)