Created
April 5, 2025 17:56
-
-
Save imaurer/ca3a49565bd52fd441afab01f85cf6a5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- `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