• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 18977819179

31 Oct 2025 03:49PM UTC coverage: 88.612% (-0.01%) from 88.623%
18977819179

push

github

web-flow
feat: added Color.random() (#3552)

Added Color.random() static method.

Updated spec to add test
Updated docs to add section for mentioning it

ran linting
ran prettier
built and tested independently

5263 of 7162 branches covered (73.49%)

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

14442 of 16298 relevant lines covered (88.61%)

24350.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/src/engine/Util/WebAudio.ts
1
import { AudioContextFactory } from '../Resources/Sound/AudioContext';
2
import { Logger } from './Log';
3

4
export interface LegacyWebAudioSource {
5
  playbackState: string;
6
  PLAYING_STATE: 'playing';
7
  FINISHED_STATE: 'finished';
8
}
9

10
/**
11
 * Patch for detecting legacy web audio in browsers
12
 * @internal
13
 * @param source
14
 */
15
function isLegacyWebAudioSource(source: any): source is LegacyWebAudioSource {
16
  return !!source.playbackState;
1✔
17
}
18

19
export class WebAudio {
248✔
20
  private static _UNLOCKED: boolean = false;
21

22
  /**
23
   * Play an empty sound to unlock Safari WebAudio context. Call this function
24
   * right after a user interaction event.
25
   * @source https://paulbakaus.com/tutorials/html5/web-audio-on-ios/
26
   */
27
  static unlock(): Promise<boolean> {
28
    const promise = new Promise<boolean>((resolve, reject) => {
17✔
29
      if (WebAudio._UNLOCKED || !AudioContextFactory.create()) {
17✔
30
        return resolve(true);
16✔
31
      }
32
      const unlockTimeoutTimer = setTimeout(() => {
1✔
33
        Logger.getInstance().warn('Excalibur was unable to unlock the audio context, audio probably will not play in this browser.');
×
34
        resolve(false);
×
35
      }, 200);
36

37
      const audioContext = AudioContextFactory.create();
1✔
38
      audioContext.resume().then(
1✔
39
        () => {
40
          // create empty buffer and play it
41
          const buffer = audioContext.createBuffer(1, 1, 22050);
1✔
42
          const source = audioContext.createBufferSource();
1✔
43
          let ended = false;
1✔
44

45
          source.buffer = buffer;
1✔
46
          source.connect(audioContext.destination);
1✔
47
          source.onended = () => (ended = true);
1✔
48

49
          source.start(0);
1✔
50

51
          // by checking the play state after some time, we know if we're really unlocked
52
          setTimeout(() => {
1✔
53
            if (isLegacyWebAudioSource(source)) {
1!
54
              if (source.playbackState === source.PLAYING_STATE || source.playbackState === source.FINISHED_STATE) {
×
55
                WebAudio._UNLOCKED = true;
×
56
              }
57
            } else {
58
              if (audioContext.currentTime > 0 || ended) {
1!
UNCOV
59
                WebAudio._UNLOCKED = true;
×
60
              }
61
            }
62
          }, 0);
63

64
          clearTimeout(unlockTimeoutTimer);
1✔
65
          resolve(true);
1✔
66
        },
67
        () => {
68
          reject();
×
69
        }
70
      );
71
    });
72

73
    return promise;
17✔
74
  }
75

76
  static isUnlocked() {
77
    return this._UNLOCKED;
×
78
  }
79
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc