I’m trying to use the optional sound settings in the BarcodeScanner class of ERPNext, as mentioned in the code comment:
// optional sound name to play when scan either fails or passes.
// see https://frappeframework.com/docs/v14/user/en/python-api/hooks#sounds
this.success_sound = opts.play_success_sound;
this.fail_sound = opts.play_fail_sound;
I would like to activate these sounds during barcode scanning (e.g. in Pick List or Stock Entry), so that:
a sound like "submit" is played on successful scans
a sound like "error" is played when the item is not found
However, most doctypes initialize BarcodeScanner internally, and I’m not sure how or where to pass these options (play_success_sound, play_fail_sound) during that process.
My question is:
How can I activate these sounds via Custom Script or config, without fully reinitializing the BarcodeScanner?
Is there a recommended way to inject these options globally or after scanner init?
…and that the methods play_success_sound() and play_fail_sound() are called inside process_scan().
But most doctypes (like Pick List) initialize the scanner internally, and there is no documented way to pass those options into the constructor from a Custom Script.
In this PR #32245, the play_success_sound and play_fail_sound options were added to the BarcodeScanner class — which is great!
But:
It’s still not clear how to properly use them in practice.
It’s quite frustrating to constantly look at the screen while scanning – especially in warehouse (Pick List) or production workflows – and sound feedback would solve that perfectly.
Can anyone help clarify
Would love some guidance or a best-practice way to get instant audio feedback when scanning – it’s a small thing but super important in daily use!
if anyone need, this code, is reading pick list positions (text2speech)
so u can pick with scanner without watching displays
ps: its in german, u can easily translate with chatgpt
// Globale Variable, in der wir unsere beste gefundene Stimme speichern
let besteStimme = null;
// Diese Funktion sucht und speichert die beste verfügbare Stimme
function findeUndLadeBesteStimme() {
const wunschStimmenListe = ["Katja", "Anna", "Google Deutsch", "Samantha", "Karen", "Daniel"];
const alleStimmen = window.speechSynthesis.getVoices();
if (alleStimmen.length === 0) { return; }
for (const wunschName of wunschStimmenListe) {
const gefundeneStimme = alleStimmen.find(stimme => stimme.name.toLowerCase().includes(wunschName.toLowerCase()));
if (gefundeneStimme) {
besteStimme = gefundeneStimme;
return;
}
}
}
window.speechSynthesis.onvoiceschanged = findeUndLadeBesteStimme;
findeUndLadeBesteStimme();
// Haupt-Sprachausgabefunktion
function speak(text_to_speak) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text_to_speak);
utterance.lang = 'de-DE';
if (besteStimme) {
utterance.voice = besteStimme;
}
window.speechSynthesis.speak(utterance);
}
// --- NEU: Hilfsfunktion zum Kürzen von Text ---
function getShortText(fullText, maxLength = 35) {
// Prüft, ob der Text existiert und länger als die maximale Länge ist
if (fullText && fullText.length > maxLength) {
// Gibt nur die ersten 'maxLength' Zeichen zurück
return fullText.substring(0, maxLength);
}
// Gibt den vollen Text zurück, wenn er kürzer oder nicht vorhanden ist
return fullText || "";
}
// --- Trigger für die Pick-Liste (mit Anpassung für gekürzten Namen) ---
frappe.ui.form.on('Pick List', {
refresh: function(frm) {
setTimeout(() => {
const first_item = frm.doc.locations.find(row => row.picked_qty < row.qty);
if (first_item) {
// ANPASSUNG: Wir verwenden die neue Kürzungs-Funktion
const itemName = getShortText(first_item.item_name);
const text = `Starte Kommissionierung. Nächster Artikel: ${itemName}, ${first_item.qty} Stück, von Lagerplatz ${first_item.warehouse}.`;
speak(text);
}
}, 500);
}
});
frappe.ui.form.on('Pick List Item', 'picked_qty', function(frm, cdt, cdn) {
const current_row = locals[cdt][cdn];
const current_index = current_row.idx;
const next_item = frm.doc.locations.find(row => row.idx > current_index && row.picked_qty < row.qty);
if (next_item) {
// ANPASSUNG: Wir verwenden die neue Kürzungs-Funktion
const itemName = getShortText(next_item.item_name);
const text = `Nächster Artikel: ${itemName}, ${next_item.qty} Stück, von Lagerplatz ${next_item.warehouse}.`;
speak(text);
} else {
speak("Alle Artikel kommissioniert. Pick-Liste abgeschlossen.");
}
});