Skip to content

Commit b156094

Browse files
committed
feat: added option to automatically cue next item (closes #28), fix: fixed a bug where selecting and audio device does not affect the sound output (closes #30), fixed a bug where linux users couldn't select removable devices (closes #31), chore: fixed translation stubs and added contributors, version bump
1 parent 4741374 commit b156094

38 files changed

Lines changed: 482 additions & 254 deletions

client/app/components/ProjectSettingsModal.vue

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@
115115
<p class="settings-help">{{ t('settings.transitionModeHelp') }}</p>
116116
</section>
117117

118+
<!-- Auto-cue next item without end behaviour (#28) -->
119+
<section class="settings-field">
120+
<label class="settings-label settings-label--checkbox">
121+
<input
122+
type="checkbox"
123+
:checked="autoCueNextWithoutEndBehavior"
124+
@change="onAutoCueNextChange"
125+
/>
126+
{{ t('settings.autoCueNext') }}
127+
</label>
128+
<p class="settings-help">{{ t('settings.autoCueNextHelp') }}</p>
129+
</section>
130+
118131
<!-- Auto volume and trim -->
119132
<section class="settings-field">
120133
<label class="settings-label settings-label--checkbox">
@@ -202,6 +215,9 @@ const disableAutoVolumeAndTrim = computed(() => !!(currentProject.value as any)?
202215
const disableLimiter = computed(() => !!(currentProject.value as any)?.settings?.disableLimiter);
203216
const disableSilenceWarning = computed(() => !!(currentProject.value as any)?.settings?.disableSilenceWarning);
204217
const defaultTransitionMode = computed(() => (currentProject.value as any)?.settings?.defaultTransitionMode || 'crossfade');
218+
// Defaults ON (undefined → true) so legacy projects and new projects both
219+
// arm the next item as "Up Next" for cues without an end behaviour. (#28)
220+
const autoCueNextWithoutEndBehavior = computed(() => (currentProject.value as any)?.settings?.autoCueNextWithoutEndBehavior !== false);
205221
const { meterMode: currentMeterMode } = useOutputTarget();
206222
const meterMode = computed(() => (currentProject.value as any)?.settings?.meterMode || currentMeterMode.value);
207223
@@ -260,6 +276,9 @@ function onDisableSilenceWarningChange(e: Event) {
260276
function onDefaultTransitionModeChange(e: Event) {
261277
applyPatch({ defaultTransitionMode: (e.target as HTMLSelectElement).value });
262278
}
279+
function onAutoCueNextChange(e: Event) {
280+
applyPatch({ autoCueNextWithoutEndBehavior: (e.target as HTMLInputElement).checked });
281+
}
263282
264283
function close() {
265284
emit('close');

client/app/components/ServerFileBrowser.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const canGoUp = computed(() =>
9494
9595
const sortedEntries = computed(() => {
9696
const entries = listing.value?.entries ?? [];
97-
const rank: Record<string, number> = { drive: 0, dir: 1, file: 2 };
97+
const rank: Record<string, number> = { home: 0, drive: 1, dir: 2, file: 3 };
9898
return [...entries].sort((a, b) => {
9999
const r = (rank[a.kind] ?? 9) - (rank[b.kind] ?? 9);
100100
return r !== 0 ? r : a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
@@ -107,6 +107,7 @@ const selectedCountLabel = computed(() =>
107107
function isSelected(p: string): boolean { return selected.value.includes(p); }
108108
109109
function iconFor(entry: ServerFsEntry): string {
110+
if (entry.kind === 'home') return 'home';
110111
if (entry.kind === 'drive') return 'storage';
111112
if (entry.kind === 'dir') return 'folder';
112113
return 'audio_file';
@@ -159,7 +160,7 @@ function onEntryClick(entry: ServerFsEntry, index: number, e: MouseEvent) {
159160
}
160161
161162
function onEntryActivate(entry: ServerFsEntry) {
162-
if (entry.kind === 'dir' || entry.kind === 'drive') goTo(entry.full_path);
163+
if (entry.kind === 'dir' || entry.kind === 'drive' || entry.kind === 'home') goTo(entry.full_path);
163164
else if (props.canSelect) emit('select', [entry.full_path]);
164165
}
165166
@@ -247,9 +248,10 @@ watch(() => props.startPath, p => goTo(p));
247248
&:last-child { border-bottom: none; }
248249
&:hover { background: #202020; }
249250
.size { color: #888; font-size: 11px; font-family: var(--font-mono); }
250-
// Names stay white for legibility; drives are emphasised.
251+
// Names stay white for legibility; drives & home are emphasised.
251252
.name { color: #ffffff; }
252-
&.drive .name { font-weight: 600; }
253+
&.drive .name,
254+
&.home .name { font-weight: 600; }
253255
// Drives & folders: white icons. Selectable files: accent icon.
254256
.icon { font-size: 18px; text-align: center; color: #ffffff; }
255257
&.file .icon { color: var(--color-accent); }

client/app/components/ServerFilePickerModal.vue

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,8 @@ const breadcrumbs = computed(() => {
192192
193193
const sortedEntries = computed(() => {
194194
const list = listing.value?.entries ?? [];
195-
// Drives first, then directories, then files. Alpha within each group.
196-
const rank: Record<string, number> = { drive: 0, dir: 1, file: 2 };
195+
// Home first, then drives, directories, files. Alpha within each group.
196+
const rank: Record<string, number> = { home: 0, drive: 1, dir: 2, file: 3 };
197197
return [...list].sort((a, b) => {
198198
const r = (rank[a.kind] ?? 9) - (rank[b.kind] ?? 9);
199199
return r !== 0 ? r : a.name.localeCompare(b.name, undefined, { sensitivity: 'base' });
@@ -215,6 +215,7 @@ const filterLabel = computed(() => filterDisplay(filter.value));
215215
// Helpers
216216
// ---------------------------------------------------------------------------
217217
function iconNameFor(entry: ServerFsEntry): string {
218+
if (entry.kind === 'home') return 'home';
218219
if (entry.kind === 'drive') return 'storage';
219220
if (entry.kind === 'dir') return 'folder';
220221
return 'description';
@@ -287,7 +288,7 @@ function onEntryClick(entry: ServerFsEntry) {
287288
}
288289
289290
function onEntryActivate(entry: ServerFsEntry) {
290-
if (entry.kind === 'dir' || entry.kind === 'drive') {
291+
if (entry.kind === 'dir' || entry.kind === 'drive' || entry.kind === 'home') {
291292
navigate(entry.full_path);
292293
} else if (entry.kind === 'file') {
293294
selected.value = entry.full_path;
@@ -427,7 +428,8 @@ watch(() => props.open, (o) => {
427428
&.file .icon { color: var(--color-accent); }
428429
.name { color: #ffffff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
429430
.size { color: #888; font-family: var(--font-mono); font-size: 11px; }
430-
&.drive .name { font-weight: 600; }
431+
&.drive .name,
432+
&.home .name { font-weight: 600; }
431433
&.selected {
432434
background: var(--color-accent);
433435
.name, .icon, .size { color: #fff; }

client/app/composables/useAudioEngine.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,20 @@ export const useAudioEngine = () => {
250250
}
251251
break;
252252
}
253+
254+
// Issue #28: an item WITHOUT an end behaviour still arms the next
255+
// playlist item as "Up Next" when the project setting is enabled
256+
// (default on, incl. legacy projects). This only arms the manual GO
257+
// target — the server never auto-advances a 'nothing' cue, so playback
258+
// still waits for the operator to press GO / spacebar. Lets an operator
259+
// step through a pre-ordered playlist with a single button.
260+
if (audioItem.endBehavior.action === 'nothing' &&
261+
(currentProject.value as any)?.settings?.autoCueNextWithoutEndBehavior !== false) {
262+
const nextIndex = [...audioItem.index];
263+
nextIndex[nextIndex.length - 1]++;
264+
const nextItem = findItemByIndex(nextIndex);
265+
if (nextItem) return nextItem.uuid;
266+
}
253267
}
254268
return null;
255269
});

client/app/types/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export interface ServerMixerChannel {
6262
export interface ServerFsEntry {
6363
name: string;
6464
full_path: string;
65-
kind: 'dir' | 'file' | 'drive'; // drive == top-level volume (Windows C:, D:, ...)
65+
kind: 'dir' | 'file' | 'drive' | 'home'; // drive == top-level volume (Windows C:, D:, ...); home == $HOME shortcut
6666
size?: number;
6767
}
6868

client/assets/json/contributors.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"Stevesibilia": {
44
"name": "Stefano Sibilia",
55
"link": "https://github.com/Stevesibilia"
6+
},
7+
"aspinwalld": {
8+
"name": "DI Aspinwall",
9+
"link": "https://github.com/aspinwalld"
610
}
711
}
812
}

client/locales/ar.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@
157157
"multiSelectOutPointDisabled": "معطّل عند تحديد عناصر متعددة (تعتمد نقطة النهاية على مدة كل عنصر).",
158158
"noWaveformData": "لا تتوفر بيانات الشكل الموجي",
159159
"ltcRequiresDevice": "اختر جهاز إخراج LTC في إعدادات المشروع لتمكين إخراج الكود الزمني.",
160-
"startNextEnable": "Start Next at Marker",
161-
"startNextTime": "Start Next At",
162-
"startNextFadeOut": "Fade Out at Marker"
160+
"startNextEnable": "بدء التالي عند العلامة",
161+
"startNextTime": "بدء التالي عند",
162+
"startNextFadeOut": "تلاشٍ خارجي عند العلامة"
163163
},
164164
"playback": {
165165
"panic": "إيقاف جميع الإشارات",
@@ -169,7 +169,7 @@
169169
"delete": "حذف",
170170
"edit": "تحرير",
171171
"masterMix": "Master",
172-
"startNextCountdown": "Time until the next track starts"
172+
"startNextCountdown": "الوقت حتى بدء المقطع التالي"
173173
},
174174
"endBehavior": {
175175
"nothing": "لا شيء",
@@ -385,10 +385,12 @@
385385
"save": "حفظ",
386386
"close": "إغلاق",
387387
"preview": "معاينة",
388-
"transitionMode": "Default Transition Mode",
389-
"transitionModeHelp": "Transition applied to newly imported tracks. Each track can still be switched between cross-fade and start-next in its properties.",
390-
"transitionModeCrossfade": "Cross-fade to next",
391-
"transitionModeStartNext": "Start next at marker"
388+
"transitionMode": "وضع الانتقال الافتراضي",
389+
"transitionModeHelp": "الانتقال المطبَّق على المقاطع المستوردة حديثًا. لا يزال بإمكان كل مقطع التبديل بين التلاشي المتقاطع وبدء التالي في خصائصه.",
390+
"transitionModeCrossfade": "تلاشٍ متقاطع إلى التالي",
391+
"transitionModeStartNext": "بدء التالي عند العلامة",
392+
"autoCueNext": "تحضير العنصر التالي تلقائيًا حتى بدون سلوك نهاية",
393+
"autoCueNextHelp": "يجهّز العنصر التالي بوصفه «التالي» حتى عندما لا يكون للعنصر قيد التشغيل سلوك نهاية، لتتمكن من التنقل في قائمة التشغيل بزر GO واحد. لا يبدأ التشغيل تلقائيًا."
392394
},
393395
"common": {
394396
"default": "افتراضي",
@@ -441,15 +443,15 @@
441443
"endGotoItem": "النهاية: الانتقال إلى العنصر",
442444
"endGotoIndex": "النهاية: الانتقال إلى الفهرس",
443445
"endLoop": "النهاية: تكرار",
444-
"startNextMarker": "Starts next track at {time}"
446+
"startNextMarker": "يبدأ المقطع التالي عند {time}"
445447
},
446448
"waveform": {
447449
"decreaseBy": "تقليل بمقدار {seconds}ث",
448450
"increaseBy": "زيادة بمقدار {seconds}ث",
449451
"playFadeTitle": "تلاشي الدخول: {seconds}ث",
450452
"stopFadeTitle": "تلاشي الخروج: {seconds}ث",
451453
"crossFadeTitle": "التلاشي المتقاطع: {seconds}ث",
452-
"startNextTitle": "Start Next: {time}"
454+
"startNextTitle": "بدء التالي: {time}"
453455
},
454456
"unsavedModal": {
455457
"title": "تغييرات غير محفوظة",

client/locales/bn.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@
157157
"multiSelectOutPointDisabled": "একাধিক নির্বাচনের জন্য নিষ্ক্রিয় (আউট পয়েন্ট প্রতিটি আইটেমের সময়কালের উপর নির্ভর করে)।",
158158
"noWaveformData": "কোনো ওয়েভফর্ম ডেটা উপলব্ধ নেই",
159159
"ltcRequiresDevice": "টাইমকোড আউটপুট সক্ষম করতে প্রকল্প সেটিংসে একটি LTC আউটপুট ডিভাইস নির্বাচন করুন।",
160-
"startNextEnable": "Start Next at Marker",
161-
"startNextTime": "Start Next At",
162-
"startNextFadeOut": "Fade Out at Marker"
160+
"startNextEnable": "মার্কারে পরবর্তী শুরু করুন",
161+
"startNextTime": "পরবর্তী শুরু হবে",
162+
"startNextFadeOut": "মার্কারে ফেড আউট"
163163
},
164164
"playback": {
165165
"panic": "সমস্ত সংকেত বন্ধ করুন",
@@ -169,7 +169,7 @@
169169
"delete": "মুছুন",
170170
"edit": "সম্পাদনা করুন",
171171
"masterMix": "Master",
172-
"startNextCountdown": "Time until the next track starts"
172+
"startNextCountdown": "পরবর্তী ট্র্যাক শুরু হতে বাকি সময়"
173173
},
174174
"endBehavior": {
175175
"nothing": "কিছুই না",
@@ -385,10 +385,12 @@
385385
"save": "সংরক্ষণ করুন",
386386
"close": "বন্ধ করুন",
387387
"preview": "পূর্বশ্রবণ",
388-
"transitionMode": "Default Transition Mode",
389-
"transitionModeHelp": "Transition applied to newly imported tracks. Each track can still be switched between cross-fade and start-next in its properties.",
390-
"transitionModeCrossfade": "Cross-fade to next",
391-
"transitionModeStartNext": "Start next at marker"
388+
"transitionMode": "ডিফল্ট ট্রানজিশন মোড",
389+
"transitionModeHelp": "নতুন আমদানি করা ট্র্যাকে প্রয়োগ করা ট্রানজিশন। প্রতিটি ট্র্যাক তার বৈশিষ্ট্যে ক্রস-ফেড এবং পরবর্তী শুরুর মধ্যে পরিবর্তন করা যাবে।",
390+
"transitionModeCrossfade": "পরবর্তীতে ক্রস-ফেড",
391+
"transitionModeStartNext": "মার্কারে পরবর্তী শুরু করুন",
392+
"autoCueNext": "শেষ আচরণ ছাড়াও পরবর্তী আইটেম স্বয়ংক্রিয়ভাবে প্রস্তুত করুন",
393+
"autoCueNextHelp": "চলমান আইটেমের শেষ আচরণ না থাকলেও পরবর্তী আইটেমকে «পরবর্তী» হিসেবে প্রস্তুত করে, যাতে আপনি একটি GO বোতামে প্লেলিস্টে এগিয়ে যেতে পারেন। এটি স্বয়ংক্রিয়ভাবে প্লেব্যাক শুরু করে না।"
392394
},
393395
"common": {
394396
"default": "ডিফল্ট",
@@ -441,15 +443,15 @@
441443
"endGotoItem": "শেষ: আইটেমে যান",
442444
"endGotoIndex": "শেষ: সূচকে যান",
443445
"endLoop": "শেষ: লুপ",
444-
"startNextMarker": "Starts next track at {time}"
446+
"startNextMarker": "{time}-এ পরবর্তী ট্র্যাক শুরু করে"
445447
},
446448
"waveform": {
447449
"decreaseBy": "{seconds}s কমান",
448450
"increaseBy": "{seconds}s বাড়ান",
449451
"playFadeTitle": "ফেড ইন: {seconds}s",
450452
"stopFadeTitle": "ফেড আউট: {seconds}s",
451453
"crossFadeTitle": "ক্রস ফেড: {seconds}s",
452-
"startNextTitle": "Start Next: {time}"
454+
"startNextTitle": "পরবর্তী শুরু: {time}"
453455
},
454456
"unsavedModal": {
455457
"title": "অসংরক্ষিত পরিবর্তন",

client/locales/de.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@
157157
"multiSelectOutPointDisabled": "Bei Mehrfachauswahl deaktiviert (der Endpunkt hängt von der Dauer des jeweiligen Elements ab).",
158158
"noWaveformData": "Keine Wellenformdaten verfügbar",
159159
"ltcRequiresDevice": "Wählen Sie in den Projekteinstellungen ein LTC-Ausgabegerät, um die Timecode-Ausgabe zu aktivieren.",
160-
"startNextEnable": "Start Next at Marker",
161-
"startNextTime": "Start Next At",
162-
"startNextFadeOut": "Fade Out at Marker"
160+
"startNextEnable": "Nächsten am Marker starten",
161+
"startNextTime": "Nächsten starten bei",
162+
"startNextFadeOut": "Am Marker ausblenden"
163163
},
164164
"playback": {
165165
"panic": "Alle Signale stoppen",
@@ -169,7 +169,7 @@
169169
"delete": "Löschen",
170170
"edit": "Bearbeiten",
171171
"masterMix": "Master",
172-
"startNextCountdown": "Time until the next track starts"
172+
"startNextCountdown": "Zeit bis zum Start des nächsten Titels"
173173
},
174174
"endBehavior": {
175175
"nothing": "Nichts",
@@ -385,10 +385,12 @@
385385
"save": "Speichern",
386386
"close": "Schließen",
387387
"preview": "Vorschau",
388-
"transitionMode": "Default Transition Mode",
389-
"transitionModeHelp": "Transition applied to newly imported tracks. Each track can still be switched between cross-fade and start-next in its properties.",
390-
"transitionModeCrossfade": "Cross-fade to next",
391-
"transitionModeStartNext": "Start next at marker"
388+
"transitionMode": "Standard-Übergangsmodus",
389+
"transitionModeHelp": "Übergang für neu importierte Titel. Jeder Titel kann in seinen Eigenschaften weiterhin zwischen Überblendung und Nächsten-Start umgeschaltet werden.",
390+
"transitionModeCrossfade": "Zum nächsten überblenden",
391+
"transitionModeStartNext": "Nächsten am Marker starten",
392+
"autoCueNext": "Nächstes Element automatisch vormerken, auch ohne Endverhalten",
393+
"autoCueNextHelp": "Merkt das nächste Element als „Als Nächstes“ vor, auch wenn das laufende Element kein Endverhalten hat, sodass Sie die Wiedergabeliste mit einer einzigen GO-Taste durchschalten können. Die Wiedergabe wird nicht automatisch gestartet."
392394
},
393395
"common": {
394396
"default": "Standard",
@@ -441,15 +443,15 @@
441443
"endGotoItem": "Ende: Zu Element springen",
442444
"endGotoIndex": "Ende: Zu Index springen",
443445
"endLoop": "Ende: Schleife",
444-
"startNextMarker": "Starts next track at {time}"
446+
"startNextMarker": "Startet den nächsten Titel bei {time}"
445447
},
446448
"waveform": {
447449
"decreaseBy": "Um {seconds}s verringern",
448450
"increaseBy": "Um {seconds}s erhöhen",
449451
"playFadeTitle": "Einblenden: {seconds}s",
450452
"stopFadeTitle": "Ausblenden: {seconds}s",
451453
"crossFadeTitle": "Überblenden: {seconds}s",
452-
"startNextTitle": "Start Next: {time}"
454+
"startNextTitle": "Nächsten starten: {time}"
453455
},
454456
"unsavedModal": {
455457
"title": "Ungespeicherte Änderungen",

0 commit comments

Comments
 (0)