sound.js 4.7 KB

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