sound.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. //
  3. // SPDX-License-Identifier: GPL-2.0-or-later
  4. 'use strict';
  5. const Gdk = imports.gi.Gdk;
  6. const Gio = imports.gi.Gio;
  7. const GLib = imports.gi.GLib;
  8. /*
  9. * Used to ensure 'audible-bell' is enabled for fallback
  10. */
  11. const WM_SETTINGS = new Gio.Settings({
  12. schema_id: 'org.gnome.desktop.wm.preferences',
  13. path: '/org/gnome/desktop/wm/preferences/',
  14. });
  15. var Player = class Player {
  16. constructor() {
  17. this._playing = new Set();
  18. }
  19. get backend() {
  20. if (this._backend === undefined) {
  21. // Prefer GSound
  22. try {
  23. this._gsound = new imports.gi.GSound.Context();
  24. this._gsound.init(null);
  25. this._backend = 'gsound';
  26. // Try falling back to libcanberra, otherwise just re-run the test
  27. // in case one or the other is installed later
  28. } catch (e) {
  29. if (GLib.find_program_in_path('canberra-gtk-play') !== null) {
  30. this._canberra = new Gio.SubprocessLauncher({
  31. flags: Gio.SubprocessFlags.NONE,
  32. });
  33. this._backend = 'libcanberra';
  34. } else {
  35. return null;
  36. }
  37. }
  38. }
  39. return this._backend;
  40. }
  41. _canberraPlaySound(name, cancellable) {
  42. const proc = this._canberra.spawnv(['canberra-gtk-play', '-i', name]);
  43. return proc.wait_check_async(cancellable);
  44. }
  45. async _canberraLoopSound(name, cancellable) {
  46. while (!cancellable.is_cancelled())
  47. await this._canberraPlaySound(name, cancellable);
  48. }
  49. _gsoundPlaySound(name, cancellable) {
  50. return new Promise((resolve, reject) => {
  51. this._gsound.play_full(
  52. {'event.id': name},
  53. cancellable,
  54. (source, res) => {
  55. try {
  56. resolve(source.play_full_finish(res));
  57. } catch (e) {
  58. reject(e);
  59. }
  60. }
  61. );
  62. });
  63. }
  64. async _gsoundLoopSound(name, cancellable) {
  65. while (!cancellable.is_cancelled())
  66. await this._gsoundPlaySound(name, cancellable);
  67. }
  68. _gdkPlaySound(name, cancellable) {
  69. if (this._display === undefined)
  70. this._display = Gdk.Display.get_default();
  71. let count = 0;
  72. GLib.timeout_add(GLib.PRIORITY_DEFAULT, 200, () => {
  73. try {
  74. if (count++ < 4 && !cancellable.is_cancelled()) {
  75. this._display.beep();
  76. return GLib.SOURCE_CONTINUE;
  77. }
  78. return GLib.SOURCE_REMOVE;
  79. } catch (e) {
  80. logError(e);
  81. return GLib.SOURCE_REMOVE;
  82. }
  83. });
  84. return !cancellable.is_cancelled();
  85. }
  86. _gdkLoopSound(name, cancellable) {
  87. this._gdkPlaySound(name, cancellable);
  88. GLib.timeout_add(
  89. GLib.PRIORITY_DEFAULT,
  90. 1500,
  91. this._gdkPlaySound.bind(this, name, cancellable)
  92. );
  93. }
  94. async playSound(name, cancellable) {
  95. try {
  96. if (!(cancellable instanceof Gio.Cancellable))
  97. cancellable = new Gio.Cancellable();
  98. this._playing.add(cancellable);
  99. switch (this.backend) {
  100. case 'gsound':
  101. await this._gsoundPlaySound(name, cancellable);
  102. break;
  103. case 'canberra':
  104. await this._canberraPlaySound(name, cancellable);
  105. break;
  106. default:
  107. await this._gdkPlaySound(name, cancellable);
  108. }
  109. } catch (e) {
  110. if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
  111. logError(e);
  112. } finally {
  113. this._playing.delete(cancellable);
  114. }
  115. }
  116. async loopSound(name, cancellable) {
  117. try {
  118. if (!(cancellable instanceof Gio.Cancellable))
  119. cancellable = new Gio.Cancellable();
  120. this._playing.add(cancellable);
  121. switch (this.backend) {
  122. case 'gsound':
  123. await this._gsoundLoopSound(name, cancellable);
  124. break;
  125. case 'canberra':
  126. await this._canberraLoopSound(name, cancellable);
  127. break;
  128. default:
  129. await this._gdkLoopSound(name, cancellable);
  130. }
  131. } catch (e) {
  132. if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
  133. logError(e);
  134. } finally {
  135. this._playing.delete(cancellable);
  136. }
  137. }
  138. destroy() {
  139. for (const cancellable of this._playing)
  140. cancellable.cancel();
  141. }
  142. };
  143. /**
  144. * The service class for this component
  145. */
  146. var Component = Player;