@laplace.live/pixijs-live2d 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -3698,6 +3698,8 @@ export declare const VOLUME = 0.5;
3698
3698
  * Manages all the sounds.
3699
3699
  */
3700
3700
  export declare class SoundManager {
3701
+ /** Select the speaker used by current and future motion audio and speak() calls. */
3702
+ static setOutputDevice(deviceId: string): Promise<void>;
3701
3703
  /**
3702
3704
  * Audio elements playing or pending to play. Finished audios will be removed automatically.
3703
3705
  */
package/dist/index.js CHANGED
@@ -3663,17 +3663,33 @@ const VOLUME = .5;
3663
3663
  const SOUND_ALIAS_PREFIX = "live2d-sound-";
3664
3664
  let soundId = 0;
3665
3665
  let pixiSoundPromise;
3666
- const configuredLibraries = /* @__PURE__ */ new WeakSet();
3666
+ const configuredLibraries = /* @__PURE__ */ new Set();
3667
+ let outputDeviceId = "";
3668
+ let outputError = null;
3669
+ /** Device changes and first-use routing run in order, so a library never settles on a stale device. */
3670
+ let routing = Promise.resolve();
3671
+ function queueRouting(task) {
3672
+ const run = routing.then(task);
3673
+ routing = run.catch(() => void 0);
3674
+ return run;
3675
+ }
3676
+ async function routeContext(context, deviceId) {
3677
+ const routable = context;
3678
+ if (routable.setSinkId) await routable.setSinkId(deviceId);
3679
+ else if (deviceId) throw new Error("Audio output selection is not supported");
3680
+ }
3667
3681
  function configureSoundLibrary(library) {
3668
- if (!configuredLibraries.has(library)) {
3682
+ return queueRouting(async () => {
3683
+ if (configuredLibraries.has(library)) return library;
3669
3684
  try {
3670
3685
  library.disableAutoPause = true;
3671
3686
  } catch (e) {
3672
3687
  logger.warn(TAG$2, "Failed to disable @pixi/sound auto pause.", e);
3673
3688
  }
3689
+ if (outputDeviceId) await routeContext(library.context.audioContext, outputDeviceId);
3674
3690
  configuredLibraries.add(library);
3675
- }
3676
- return library;
3691
+ return library;
3692
+ });
3677
3693
  }
3678
3694
  function getGlobalSoundLibrary() {
3679
3695
  return typeof PIXI !== "undefined" ? PIXI?.sound : void 0;
@@ -3681,11 +3697,12 @@ function getGlobalSoundLibrary() {
3681
3697
  async function resolveSoundLibrary() {
3682
3698
  const globalLibrary = getGlobalSoundLibrary();
3683
3699
  if (globalLibrary) return configureSoundLibrary(globalLibrary);
3684
- pixiSoundPromise ??= import("@pixi/sound").then(({ sound }) => configureSoundLibrary(sound)).catch((e) => {
3700
+ pixiSoundPromise ??= import("@pixi/sound").then(({ sound }) => sound).catch((e) => {
3685
3701
  logger.warn(TAG$2, "@pixi/sound is not available. Load pixi-sound.js before using motion sounds, speak(), or lip sync.", e);
3686
3702
  return null;
3687
3703
  });
3688
- return pixiSoundPromise;
3704
+ const library = await pixiSoundPromise;
3705
+ return library ? configureSoundLibrary(library) : null;
3689
3706
  }
3690
3707
  function removeSound(library, alias) {
3691
3708
  try {
@@ -3710,6 +3727,25 @@ function getAudioBuffer(audio) {
3710
3727
  * Manages all the sounds.
3711
3728
  */
3712
3729
  var SoundManager = class SoundManager {
3730
+ /** Select the speaker used by current and future motion audio and speak() calls. */
3731
+ static setOutputDevice(deviceId) {
3732
+ return queueRouting(async () => {
3733
+ const changed = [];
3734
+ try {
3735
+ for (const library of configuredLibraries) {
3736
+ const context = library.context.audioContext;
3737
+ await routeContext(context, deviceId);
3738
+ changed.push(context);
3739
+ }
3740
+ outputDeviceId = deviceId;
3741
+ outputError = null;
3742
+ } catch (error) {
3743
+ outputError = error;
3744
+ await Promise.allSettled(changed.map((context) => routeContext(context, outputDeviceId)));
3745
+ throw error;
3746
+ }
3747
+ });
3748
+ }
3713
3749
  /**
3714
3750
  * Audio elements playing or pending to play. Finished audios will be removed automatically.
3715
3751
  */
@@ -3740,6 +3776,7 @@ var SoundManager = class SoundManager {
3740
3776
  let library = null;
3741
3777
  let alias;
3742
3778
  try {
3779
+ if (outputError) throw outputError;
3743
3780
  library = await resolveSoundLibrary();
3744
3781
  if (!library) throw new Error("@pixi/sound is not available");
3745
3782
  alias = `${SOUND_ALIAS_PREFIX}${soundId++}`;
@@ -3768,6 +3805,7 @@ var SoundManager = class SoundManager {
3768
3805
  }
3769
3806
  });
3770
3807
  });
3808
+ if (outputError) throw outputError;
3771
3809
  SoundManager.aliases.set(audio, {
3772
3810
  alias: soundAlias,
3773
3811
  library: soundLibrary
@@ -3787,6 +3825,10 @@ var SoundManager = class SoundManager {
3787
3825
  * @param onFinish - Callback invoked when the playback has finished.
3788
3826
  */
3789
3827
  static play(audio, onFinish) {
3828
+ if (outputError) {
3829
+ SoundManager.dispose(audio);
3830
+ return;
3831
+ }
3790
3832
  audio.play({
3791
3833
  singleInstance: true,
3792
3834
  complete: () => {