Skip to content

Commit ec839cf

Browse files
committed
updates to audio, audio player, and rss feed
1 parent 6302ec1 commit ec839cf

109 files changed

Lines changed: 1051 additions & 191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Chapter01/universe-of-life.ipynb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@
3232
"```"
3333
]
3434
},
35+
{
36+
"cell_type": "markdown",
37+
"metadata": {
38+
"tags": [
39+
"skip-tts"
40+
]
41+
},
42+
"source": [
43+
"## Course audiobook feed\n",
44+
"\n",
45+
"You can listen to the course audio as a podcast feed:\n",
46+
"\n",
47+
"[Subscribe to the Life in the Universe audiobook feed](https://saturnaxis.github.io/LifeInTheUniverse/podcast.xml)"
48+
]
49+
},
3550
{
3651
"cell_type": "markdown",
3752
"metadata": {},

Chapter09/jovian-moons.ipynb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,37 @@
427427
"_This narration is AI-generated from the course text._"
428428
]
429429
},
430+
{
431+
"cell_type": "markdown",
432+
"id": "3845e46f",
433+
"metadata": {},
434+
"source": [
435+
"```{raw} html\n",
436+
"<div class=\"audio-card\">\n",
437+
" <div class=\"audio-toolbar\">\n",
438+
" <label class=\"audio-field chapter-field\">\n",
439+
" <span>Chapter</span>\n",
440+
" <select class=\"chapter-select\">\n",
441+
" <option value=\"7\">Europa as an ocean world — 0:07</option>\n",
442+
" <option value=\"513\">Could Europa have life? — 8:33</option>\n",
443+
" <option value=\"1064\">Ganymede and Callisto — 17:44</option>\n",
444+
" </select>\n",
445+
" </label>\n",
446+
"\n",
447+
" <div class=\"chapter-buttons\">\n",
448+
" <button type=\"button\" class=\"prev-chapter\">← Previous</button>\n",
449+
" <button type=\"button\" class=\"next-chapter\">Next →</button>\n",
450+
" </div>\n",
451+
" </div>\n",
452+
"\n",
453+
" <audio class=\"audio-el\" controls preload=\"metadata\">\n",
454+
" <source src=\"../_static/audio/Chapter09_jovian-moons__9-2-life-on-jupiter-s-galilean-moons.mp3\" type=\"audio/mpeg\">\n",
455+
" Your browser does not support the audio element.\n",
456+
" </audio>\n",
457+
"</div>\n",
458+
"```"
459+
]
460+
},
430461
{
431462
"cell_type": "markdown",
432463
"id": "06daced9",

_config.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ sphinx:
8585
html_css_files:
8686
- "https://cdn.plyr.io/3.8.4/plyr.css"
8787
- "custom.css"
88+
- "audio-player.css"
8889
html_js_files:
8990
- "https://cdn.plyr.io/3.8.4/plyr.polyfilled.js"
9091
- "init-plyr.js"
92+
- "audio-player.js"
93+
html_extra_path:
94+
- "podcast.xml"

_static/audio-player.css

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/* ETAMU Audio Player Theme */
2+
:root {
3+
--audio-gold: #F2A900;
4+
--audio-blue: #0033A0;
5+
--audio-blue-dark: #002575;
6+
--audio-white: #FFFFFF;
7+
--audio-dark-text: #1f2937;
8+
}
9+
10+
/* Main card */
11+
.audio-card {
12+
box-sizing: border-box;
13+
width: 100%;
14+
max-width: 900px;
15+
margin: 1.5rem 0;
16+
padding: 1.25rem;
17+
border: 3px solid var(--audio-gold);
18+
border-radius: 18px;
19+
background: var(--audio-blue);
20+
color: var(--audio-white);
21+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
22+
23+
display: flex;
24+
flex-direction: column;
25+
gap: 1.25rem;
26+
}
27+
28+
.audio-card *,
29+
.audio-card *::before,
30+
.audio-card *::after {
31+
box-sizing: border-box;
32+
}
33+
34+
/* Native browser audio bar */
35+
.audio-card audio {
36+
width: 100%;
37+
display: block;
38+
border-radius: 999px;
39+
}
40+
41+
/* Wrapping toolbar */
42+
.audio-toolbar {
43+
display: flex;
44+
flex-wrap: wrap;
45+
gap: 0.85rem;
46+
align-items: flex-end;
47+
}
48+
49+
/* Labels */
50+
.audio-field {
51+
display: flex;
52+
flex-direction: column;
53+
gap: 0.35rem;
54+
color: var(--audio-white);
55+
font-size: 0.95rem;
56+
font-weight: 800;
57+
}
58+
59+
/* Chapter selector */
60+
.chapter-field {
61+
flex: 1 1 260px;
62+
min-width: 220px;
63+
}
64+
65+
/* Speed selector */
66+
.small-field,
67+
.speed-field {
68+
flex: 0 0 120px;
69+
}
70+
71+
/* Volume */
72+
.volume-field {
73+
flex: 1 1 210px;
74+
min-width: 180px;
75+
}
76+
77+
/* Select menus */
78+
.audio-field select {
79+
width: 100%;
80+
height: 2.5rem;
81+
border: 2px solid var(--audio-gold);
82+
border-radius: 10px;
83+
background: var(--audio-white);
84+
color: var(--audio-dark-text);
85+
padding: 0 0.75rem;
86+
font-size: 1rem;
87+
}
88+
89+
/* Chapter buttons */
90+
.chapter-buttons {
91+
display: flex;
92+
gap: 0.5rem;
93+
flex: 0 0 auto;
94+
}
95+
96+
.chapter-buttons button {
97+
height: 2.5rem;
98+
border: 2px solid var(--audio-gold);
99+
border-radius: 10px;
100+
background: var(--audio-gold);
101+
color: var(--audio-dark-text);
102+
padding: 0 0.9rem;
103+
font-size: 1rem;
104+
font-weight: 800;
105+
cursor: pointer;
106+
white-space: nowrap;
107+
}
108+
109+
.chapter-buttons button:hover {
110+
filter: brightness(1.08);
111+
}
112+
113+
/* Volume slider */
114+
.volume-slider {
115+
width: 100%;
116+
accent-color: var(--audio-gold);
117+
}
118+
119+
/* Time display */
120+
.time-display {
121+
flex: 0 0 auto;
122+
margin-left: auto;
123+
padding: 0.55rem 0.75rem;
124+
border-radius: 999px;
125+
background: var(--audio-blue-dark);
126+
color: var(--audio-white);
127+
font-variant-numeric: tabular-nums;
128+
font-size: 0.95rem;
129+
font-weight: 800;
130+
white-space: nowrap;
131+
}
132+
133+
/* Focus states */
134+
.audio-field select:focus,
135+
.chapter-buttons button:focus,
136+
.volume-slider:focus {
137+
outline: 3px solid var(--audio-gold);
138+
outline-offset: 2px;
139+
}
140+
141+
/* Mobile */
142+
@media (max-width: 700px) {
143+
.audio-card {
144+
padding: 1rem;
145+
}
146+
147+
.audio-toolbar {
148+
flex-direction: column;
149+
align-items: stretch;
150+
}
151+
152+
.chapter-buttons {
153+
width: 100%;
154+
}
155+
156+
.chapter-buttons button {
157+
flex: 1;
158+
}
159+
160+
.time-display {
161+
margin-left: 0;
162+
text-align: center;
163+
}
164+
}

_static/audio-player.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
(function () {
2+
function formatTime(seconds) {
3+
if (!Number.isFinite(seconds)) return "0:00";
4+
const mins = Math.floor(seconds / 60);
5+
const secs = Math.floor(seconds % 60).toString().padStart(2, "0");
6+
return `${mins}:${secs}`;
7+
}
8+
9+
function initAudioCard(card) {
10+
if (card.dataset.initialized === "true") return;
11+
card.dataset.initialized = "true";
12+
13+
const audio = card.querySelector(".audio-el");
14+
const chapterSelect = card.querySelector(".chapter-select");
15+
const prevButton = card.querySelector(".prev-chapter");
16+
const nextButton = card.querySelector(".next-chapter");
17+
const speedSelect = card.querySelector(".speed-select");
18+
const volumeSlider = card.querySelector(".volume-slider");
19+
const timeDisplay = card.querySelector(".time-display");
20+
21+
if (!audio || !chapterSelect) return;
22+
23+
const chapters = Array.from(chapterSelect.options).map(option =>
24+
Number(option.value)
25+
);
26+
27+
function getCurrentChapterIndex() {
28+
let index = 0;
29+
for (let i = 0; i < chapters.length; i++) {
30+
if (audio.currentTime >= chapters[i]) index = i;
31+
}
32+
return index;
33+
}
34+
35+
function seekToChapter(index) {
36+
const safeIndex = Math.max(0, Math.min(index, chapters.length - 1));
37+
audio.currentTime = chapters[safeIndex];
38+
chapterSelect.selectedIndex = safeIndex;
39+
}
40+
41+
function updateTime() {
42+
chapterSelect.selectedIndex = getCurrentChapterIndex();
43+
timeDisplay.textContent =
44+
`${formatTime(audio.currentTime)} / ${formatTime(audio.duration)}`;
45+
}
46+
47+
chapterSelect.addEventListener("change", function () {
48+
audio.currentTime = Number(chapterSelect.value);
49+
});
50+
51+
prevButton.addEventListener("click", function () {
52+
const index = getCurrentChapterIndex();
53+
54+
if (audio.currentTime - chapters[index] > 3) {
55+
seekToChapter(index);
56+
} else {
57+
seekToChapter(index - 1);
58+
}
59+
});
60+
61+
nextButton.addEventListener("click", function () {
62+
seekToChapter(getCurrentChapterIndex() + 1);
63+
});
64+
65+
speedSelect.addEventListener("change", function () {
66+
audio.playbackRate = Number(speedSelect.value);
67+
});
68+
69+
volumeSlider.addEventListener("input", function () {
70+
audio.volume = Number(volumeSlider.value);
71+
});
72+
73+
audio.addEventListener("loadedmetadata", updateTime);
74+
audio.addEventListener("timeupdate", updateTime);
75+
}
76+
77+
function initAllAudioCards() {
78+
document.querySelectorAll(".audio-card").forEach(initAudioCard);
79+
}
80+
81+
if (document.readyState === "loading") {
82+
document.addEventListener("DOMContentLoaded", initAllAudioCards);
83+
} else {
84+
initAllAudioCards();
85+
}
86+
})();

0 commit comments

Comments
 (0)