// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; let GSound = null; try { GSound = (await import('gi://GSound')).default; } catch (e) {} const Player = class Player { constructor() { this._playing = new Set(); } get backend() { if (this._backend === undefined) { // Prefer GSound if (GSound !== null) { this._gsound = new GSound.Context(); this._gsound.init(null); this._backend = 'gsound'; // Try falling back to libcanberra, otherwise just re-run the test // in case one or the other is installed later } else if (GLib.find_program_in_path('canberra-gtk-play') !== null) { this._canberra = new Gio.SubprocessLauncher({ flags: Gio.SubprocessFlags.NONE, }); this._backend = 'libcanberra'; } else { return null; } } return this._backend; } _canberraPlaySound(name, cancellable) { const proc = this._canberra.spawnv(['canberra-gtk-play', '-i', name]); return proc.wait_check_async(cancellable); } async _canberraLoopSound(name, cancellable) { while (!cancellable.is_cancelled()) await this._canberraPlaySound(name, cancellable); } _gsoundPlaySound(name, cancellable) { return new Promise((resolve, reject) => { this._gsound.play_full( {'event.id': name}, cancellable, (source, res) => { try { resolve(source.play_full_finish(res)); } catch (e) { reject(e); } } ); }); } async _gsoundLoopSound(name, cancellable) { while (!cancellable.is_cancelled()) await this._gsoundPlaySound(name, cancellable); } _gdkPlaySound(name, cancellable) { if (this._display === undefined) this._display = Gdk.Display.get_default(); let count = 0; GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => { try { if (count++ < 4 && !cancellable.is_cancelled()) { this._display.beep(); return GLib.SOURCE_CONTINUE; } return GLib.SOURCE_REMOVE; } catch (e) { logError(e); return GLib.SOURCE_REMOVE; } }); return !cancellable.is_cancelled(); } _gdkLoopSound(name, cancellable) { this._gdkPlaySound(name, cancellable); GLib.timeout_add( GLib.PRIORITY_DEFAULT, 1500, this._gdkPlaySound.bind(this, name, cancellable) ); } async playSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); switch (this.backend) { case 'gsound': await this._gsoundPlaySound(name, cancellable); break; case 'canberra': await this._canberraPlaySound(name, cancellable); break; default: await this._gdkPlaySound(name, cancellable); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } async loopSound(name, cancellable) { try { if (!(cancellable instanceof Gio.Cancellable)) cancellable = new Gio.Cancellable(); this._playing.add(cancellable); switch (this.backend) { case 'gsound': await this._gsoundLoopSound(name, cancellable); break; case 'canberra': await this._canberraLoopSound(name, cancellable); break; default: await this._gdkLoopSound(name, cancellable); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) logError(e); } finally { this._playing.delete(cancellable); } } destroy() { for (const cancellable of this._playing) cancellable.cancel(); } }; /** * The service class for this component */ export default Player;