Skip to content

Instantly share code, notes, and snippets.

@imaurer
Created April 5, 2025 17:56
Show Gist options
  • Save imaurer/ca3a49565bd52fd441afab01f85cf6a5 to your computer and use it in GitHub Desktop.
Save imaurer/ca3a49565bd52fd441afab01f85cf6a5 to your computer and use it in GitHub Desktop.
<!-- `https://api.substack.com/feed/podcast/160495909/bdd3acc7cd18a69c68ad250654009252.mp3` -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Podcast MP3 Player</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f7;
color: #333;
}
.player-container {
background-color: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
h1 {
font-size: 1.5rem;
margin-top: 0;
color: #333;
}
.episode-title {
font-weight: 600;
margin-bottom: 20px;
}
.controls {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.play-pause {
background-color: #0071e3;
color: white;
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
font-size: 16px;
cursor: pointer;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.play-pause:hover {
background-color: #0058a8;
}
.volume-control {
display: flex;
align-items: center;
margin-left: auto;
}
.volume-icon {
margin-right: 8px;
}
input[type="range"] {
-webkit-appearance: none;
height: 5px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
background: #0071e3;
cursor: pointer;
}
.progress-container {
width: 100%;
height: 5px;
background-color: #e0e0e0;
border-radius: 5px;
margin-bottom: 10px;
cursor: pointer;
}
.progress-bar {
height: 100%;
background-color: #0071e3;
border-radius: 5px;
width: 0%;
}
.time-display {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #666;
}
.loading-message {
text-align: center;
margin: 20px 0;
color: #666;
}
.error-message {
color: #d32f2f;
text-align: center;
margin: 20px 0;
display: none;
}
</style>
</head>
<body>
<div class="player-container">
<h1>Dwarkesh Podcast Player</h1>
<div class="episode-title">2027 Intelligence Explosion: Month-by-Month Model — Scott Alexander & Daniel Kokotajlo</div>
<div class="loading-message" id="loadingMessage">Loading podcast episode...</div>
<div class="error-message" id="errorMessage">
Unable to load the podcast due to cross-origin restrictions.
In a real application, this would require server-side handling.
</div>
<div id="playerControls" style="display: none;">
<div class="progress-container" id="progressContainer">
<div class="progress-bar" id="progressBar"></div>
</div>
<div class="time-display">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
<div class="controls">
<button class="play-pause" id="playPauseBtn">▶</button>
<div class="volume-control">
<span class="volume-icon">🔊</span>
<input type="range" id="volumeControl" min="0" max="1" step="0.01" value="0.7">
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const audioUrl = 'https://api.substack.com/feed/podcast/160495909/bdd3acc7cd18a69c68ad250654009252.mp3';
const audio = new Audio();
const playPauseBtn = document.getElementById('playPauseBtn');
const volumeControl = document.getElementById('volumeControl');
const progressBar = document.getElementById('progressBar');
const progressContainer = document.getElementById('progressContainer');
const currentTimeDisplay = document.getElementById('currentTime');
const durationDisplay = document.getElementById('duration');
const loadingMessage = document.getElementById('loadingMessage');
const errorMessage = document.getElementById('errorMessage');
const playerControls = document.getElementById('playerControls');
// Set audio source
audio.src = audioUrl;
audio.volume = volumeControl.value;
// Load audio metadata
audio.addEventListener('loadedmetadata', function() {
loadingMessage.style.display = 'none';
playerControls.style.display = 'block';
durationDisplay.textContent = formatTime(audio.duration);
});
// Handle loading error (likely due to CORS)
audio.addEventListener('error', function() {
loadingMessage.style.display = 'none';
errorMessage.style.display = 'block';
// For demo purposes, we'll show the player anyway
setTimeout(() => {
errorMessage.textContent += " (Showing mock player for demonstration)";
playerControls.style.display = 'block';
durationDisplay.textContent = "3:04:26"; // Example duration
}, 2000);
});
// Play/Pause button
playPauseBtn.addEventListener('click', function() {
if (audio.paused) {
audio.play().then(() => {
playPauseBtn.innerHTML = '❚❚';
}).catch(error => {
console.error("Playback prevented:", error);
// Simulate playback for demo
playPauseBtn.innerHTML = '❚❚';
simulatePlayback();
});
} else {
audio.pause();
playPauseBtn.innerHTML = '▶';
}
});
// Update progress bar
audio.addEventListener('timeupdate', function() {
const percent = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = percent + '%';
currentTimeDisplay.textContent = formatTime(audio.currentTime);
});
// Allow seeking
progressContainer.addEventListener('click', function(e) {
const percent = (e.offsetX / this.offsetWidth);
audio.currentTime = percent * audio.duration;
});
// Volume control
volumeControl.addEventListener('input', function() {
audio.volume = this.value;
});
// Format time to MM:SS
function formatTime(seconds) {
if (isNaN(seconds)) return "0:00";
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hrs > 0) {
return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
} else {
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
// For demo purposes only - simulate playback when real playback fails
let simulatedCurrentTime = 0;
let simulationInterval;
function simulatePlayback() {
simulationInterval = setInterval(() => {
simulatedCurrentTime += 1;
const percent = (simulatedCurrentTime / 11066) * 100; // Using actual duration from RSS
progressBar.style.width = percent + '%';
currentTimeDisplay.textContent = formatTime(simulatedCurrentTime);
if (simulatedCurrentTime >= 11066) {
clearInterval(simulationInterval);
playPauseBtn.innerHTML = '▶';
}
}, 1000);
}
playPauseBtn.addEventListener('click', function() {
if (simulationInterval) {
clearInterval(simulationInterval);
simulationInterval = null;
playPauseBtn.innerHTML = '▶';
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment