Skip to content

Commit 3f5ef11

Browse files
committed
Merge branch 'master' into release
2 parents 24878e6 + 3c1fe9f commit 3f5ef11

13 files changed

Lines changed: 625 additions & 19 deletions

File tree

.github/workflows/publish-to-auto-release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
platform: [macos-14, macos-latest, ubuntu-20.04, windows-latest]
18+
platform: [macos-14, macos-latest, ubuntu-22.04, windows-latest]
1919

2020
runs-on: ${{ matrix.platform }}
2121
steps:
@@ -30,7 +30,7 @@ jobs:
3030
uses: dtolnay/rust-toolchain@stable
3131

3232
- name: install dependencies (ubuntu only)
33-
if: matrix.platform == 'ubuntu-20.04'
33+
if: matrix.platform == 'ubuntu-22.04'
3434
run: |
3535
sudo apt-get update
3636
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "mp3chapters-player",
33
"private": true,
4-
"version": "0.3.1",
4+
"version": "0.4.0",
55
"type": "module",
66
"scripts": {
77
"tauri": "webpack --config webpack.tauri.config.js && tauri",

src-app/index.html

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,156 @@
177177
text-overflow: ellipsis;
178178
white-space: nowrap;
179179
}
180+
181+
/* Keyboard Shortcuts Panel */
182+
.shortcuts-panel {
183+
position: fixed;
184+
top: 0;
185+
left: 0;
186+
width: 100%;
187+
height: 100%;
188+
background: rgba(0, 0, 0, 0.85);
189+
display: flex;
190+
justify-content: center;
191+
align-items: center;
192+
z-index: 1000;
193+
opacity: 1;
194+
transition: opacity 0.2s ease;
195+
}
196+
197+
.shortcuts-panel.hidden {
198+
opacity: 0;
199+
pointer-events: none;
200+
}
201+
202+
.shortcuts-content {
203+
background: #1c1c1c;
204+
border-radius: 12px;
205+
padding: 24px;
206+
max-width: 600px;
207+
max-height: 80vh;
208+
overflow-y: auto;
209+
border: 1px solid #444;
210+
}
211+
212+
.shortcuts-header {
213+
display: flex;
214+
justify-content: space-between;
215+
align-items: center;
216+
margin-bottom: 20px;
217+
border-bottom: 1px solid #333;
218+
padding-bottom: 12px;
219+
}
220+
221+
.shortcuts-header h2 {
222+
margin: 0;
223+
color: #fff;
224+
font-size: 1.3em;
225+
font-weight: 500;
226+
}
227+
228+
.shortcuts-close {
229+
background: none;
230+
border: none;
231+
color: #888;
232+
font-size: 1.5em;
233+
cursor: pointer;
234+
padding: 4px 8px;
235+
border-radius: 4px;
236+
}
237+
238+
.shortcuts-close:hover {
239+
background: #333;
240+
color: #fff;
241+
}
242+
243+
.shortcuts-grid {
244+
display: grid;
245+
grid-template-columns: repeat(2, 1fr);
246+
gap: 20px;
247+
}
248+
249+
.shortcut-section h3 {
250+
color: #888;
251+
font-size: 0.85em;
252+
text-transform: uppercase;
253+
letter-spacing: 0.5px;
254+
margin: 0 0 10px 0;
255+
}
256+
257+
.shortcut-row {
258+
display: flex;
259+
justify-content: space-between;
260+
align-items: center;
261+
padding: 6px 0;
262+
color: #ccc;
263+
font-size: 0.9em;
264+
}
265+
266+
.shortcut-row span {
267+
color: #999;
268+
}
269+
270+
kbd {
271+
background: #333;
272+
border: 1px solid #555;
273+
border-radius: 4px;
274+
padding: 2px 8px;
275+
font-family: inherit;
276+
font-size: 0.85em;
277+
color: #fff;
278+
min-width: 24px;
279+
text-align: center;
280+
display: inline-block;
281+
}
180282
</style>
181283
</head>
182284

183285
<body>
286+
<!-- Keyboard Shortcuts Panel -->
287+
<div id="shortcuts-panel" class="shortcuts-panel hidden">
288+
<div class="shortcuts-content">
289+
<div class="shortcuts-header">
290+
<h2>Keyboard Shortcuts</h2>
291+
<button class="shortcuts-close" onclick="window.toggleShortcutsPanel()">&times;</button>
292+
</div>
293+
<div class="shortcuts-grid">
294+
<div class="shortcut-section">
295+
<h3>Playback</h3>
296+
<div class="shortcut-row"><kbd>Space</kbd> or <kbd>K</kbd><span>Play / Pause</span></div>
297+
<div class="shortcut-row"><kbd>M</kbd><span>Mute / Unmute</span></div>
298+
<div class="shortcut-row"><kbd>Home</kbd><span>Go to beginning</span></div>
299+
<div class="shortcut-row"><kbd>End</kbd><span>Go to end</span></div>
300+
</div>
301+
<div class="shortcut-section">
302+
<h3>Seeking</h3>
303+
<div class="shortcut-row"><kbd>&larr;</kbd> or <kbd>J</kbd><span>Rewind 10s</span></div>
304+
<div class="shortcut-row"><kbd>&rarr;</kbd> or <kbd>L</kbd><span>Forward 10s</span></div>
305+
<div class="shortcut-row"><kbd>0</kbd>-<kbd>9</kbd><span>Jump to 0%-90%</span></div>
306+
</div>
307+
<div class="shortcut-section">
308+
<h3>Volume</h3>
309+
<div class="shortcut-row"><kbd>&uarr;</kbd><span>Volume up</span></div>
310+
<div class="shortcut-row"><kbd>&darr;</kbd><span>Volume down</span></div>
311+
</div>
312+
<div class="shortcut-section">
313+
<h3>Speed</h3>
314+
<div class="shortcut-row"><kbd>[</kbd> or <kbd>,</kbd><span>Slower</span></div>
315+
<div class="shortcut-row"><kbd>]</kbd> or <kbd>.</kbd><span>Faster</span></div>
316+
</div>
317+
<div class="shortcut-section">
318+
<h3>Chapters</h3>
319+
<div class="shortcut-row"><kbd>N</kbd> or <kbd>PgDn</kbd><span>Next chapter</span></div>
320+
<div class="shortcut-row"><kbd>P</kbd> or <kbd>PgUp</kbd><span>Previous chapter</span></div>
321+
</div>
322+
<div class="shortcut-section">
323+
<h3>Help</h3>
324+
<div class="shortcut-row"><kbd>?</kbd><span>Toggle this panel</span></div>
325+
</div>
326+
</div>
327+
</div>
328+
</div>
329+
184330
<div class="container">
185331
<div class="top">
186332
<img

src-app/main.js

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@ import { getMatches } from '@tauri-apps/api/cli';
1010
import { listen } from '@tauri-apps/api/event';
1111

1212
import { loadFile } from '@common/FileLoader.js';
13-
import { startUp } from '@common/Player.js';
13+
import { startUp, toggleShortcutsPanel, goToNextChapter, goToPreviousChapter, SEEK_SECONDS, SPEED_STEP, MIN_SPEED, MAX_SPEED, VOLUME_STEP } from '@common/Player.js';
1414

15-
function handleMP3FilePath(filePath) {
15+
async function handleMP3FilePath(filePath) {
1616
const url = convertFileSrc(filePath);
1717
console.log("Opening file: " + url);
1818
player.src = { src: url, type: 'audio/mpeg' };
19-
loadFile(player, url);
2019
const fileName = filePath.split('\\').pop().split('/').pop();
21-
appWindow.setTitle(fileName);
20+
const { id3Title } = await loadFile(player, url);
21+
if (id3Title) {
22+
appWindow.setTitle(`${id3Title} [${fileName}]`);
23+
} else {
24+
appWindow.setTitle(fileName);
25+
}
2226
}
2327

2428
listen('tauri://file-drop', event => {
@@ -49,8 +53,60 @@ async function openFile() {
4953

5054
const unlisten = await appWindow.onMenuClicked(async ({ payload: menuId }) => {
5155
console.log('Menu clicked: ' + menuId);
52-
if (menuId == 'open') {
53-
openFile();
56+
switch (menuId) {
57+
case 'open':
58+
openFile();
59+
break;
60+
case 'keyboard_shortcuts':
61+
toggleShortcutsPanel();
62+
break;
63+
// Playback controls
64+
case 'play_pause':
65+
if (player.paused) {
66+
player.play();
67+
} else {
68+
player.pause();
69+
}
70+
break;
71+
case 'mute':
72+
player.muted = !player.muted;
73+
break;
74+
case 'seek_forward':
75+
player.currentTime = Math.min(player.duration, player.currentTime + SEEK_SECONDS);
76+
break;
77+
case 'seek_backward':
78+
player.currentTime = Math.max(0, player.currentTime - SEEK_SECONDS);
79+
break;
80+
case 'go_to_beginning':
81+
player.currentTime = 0;
82+
break;
83+
case 'go_to_end':
84+
player.currentTime = player.duration;
85+
break;
86+
// Volume controls
87+
case 'volume_up':
88+
player.volume = Math.min(1, player.volume + VOLUME_STEP);
89+
break;
90+
case 'volume_down':
91+
player.volume = Math.max(0, player.volume - VOLUME_STEP);
92+
break;
93+
// Speed controls
94+
case 'increase_speed':
95+
player.playbackRate = Math.min(MAX_SPEED, player.playbackRate + SPEED_STEP);
96+
break;
97+
case 'decrease_speed':
98+
player.playbackRate = Math.max(MIN_SPEED, player.playbackRate - SPEED_STEP);
99+
break;
100+
case 'reset_speed':
101+
player.playbackRate = 1.0;
102+
break;
103+
// Chapter navigation
104+
case 'next_chapter':
105+
goToNextChapter(player, window.chapters);
106+
break;
107+
case 'previous_chapter':
108+
goToPreviousChapter(player, window.chapters);
109+
break;
54110
}
55111
});
56112

src-common/ChapterList.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,29 @@ export class ChapterList {
7272
return undefined;
7373
}
7474

75+
// Get the next chapter after the given time (only TOC entries)
76+
nextChapterAfterTime(time) {
77+
for (let chapter of this.chapters) {
78+
if (chapter.toc && chapter.start > time) {
79+
return chapter;
80+
}
81+
}
82+
return undefined;
83+
}
84+
85+
// Get the previous chapter before the given time (only TOC entries)
86+
previousChapterBeforeTime(time) {
87+
let prevChapter = undefined;
88+
for (let chapter of this.chapters) {
89+
if (chapter.toc && chapter.start < time) {
90+
prevChapter = chapter;
91+
} else if (chapter.start >= time) {
92+
break;
93+
}
94+
}
95+
return prevChapter;
96+
}
97+
7598
// Method to get chapters
7699
getChapters() {
77100
return this.chapters;

src-common/FileLoader.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ export async function loadFile(player, url) {
1717
window.currentFile = file;
1818
window.chapters.duration = -1;
1919
window.chapterImages = [];
20+
window.id3Title = null; // Will be set if ID3 title tag exists
2021

2122
let tags = await new Promise((resolve) => {
2223
readTags(file, (fileTags) => {
2324
resolve(fileTags);
2425
});
2526
});
2627

28+
// Extract ID3 title if available
29+
if (tags.hasOwnProperty('title') && tags.title) {
30+
window.id3Title = tags.title;
31+
}
32+
2733
// console.log(tags);
2834
let toc = [];
2935
if (tags.hasOwnProperty('tableOfContents') && tags.tableOfContents.length > 0 && tags.tableOfContents[0].elements) {
@@ -96,4 +102,7 @@ export async function loadFile(player, url) {
96102
// buildGallery();
97103

98104
// document.getElementById('filename').innerText = file.name;
105+
106+
// Return the ID3 title for use by callers (e.g., setting window title)
107+
return { id3Title: window.id3Title };
99108
}

0 commit comments

Comments
 (0)