Uploaded image for project: 'Minecraft: Java Edition'
  1. Minecraft: Java Edition
  2. MC-35714

Sounds loop/restart when exiting screens

    Details

    • Type: Bug
    • Status: Resolved
    • Resolution: Fixed
    • Affects Version/s: Minecraft 13w42a, Minecraft 13w42b, Minecraft 13w43a, Minecraft 1.7, Minecraft 1.7.1, Minecraft 1.7.2, Minecraft 13w47e, Minecraft 13w48a, Minecraft 13w48b, Minecraft 13w49a, Minecraft 1.7.3, Minecraft 1.7.4, Minecraft 14w02a, Minecraft 14w02b, Minecraft 14w03b, Minecraft 14w04b, Minecraft 14w05b, Minecraft 14w06a, Minecraft 14w06b, Minecraft 14w07a, Minecraft 14w08a, Minecraft 1.7.5, Minecraft 14w10b, Minecraft 14w10c, Minecraft 14w11b, Minecraft 1.7.9, Minecraft 14w19a, Minecraft 14w20b, Minecraft 14w21a, Minecraft 14w21b, Minecraft 1.7.10-pre1, Minecraft 1.7.10-pre2, Minecraft 14w25a, Minecraft 14w25b, Minecraft 1.7.10, Minecraft 14w30b, Minecraft 14w30c, Minecraft 14w33c, Minecraft 14w34a, Minecraft 14w34b, Minecraft 14w34c, Minecraft 14w34d, Minecraft 1.8-pre1, Minecraft 1.8-pre2, Minecraft 1.8-pre3, Minecraft 1.8, Minecraft 1.8.1-pre1, Minecraft 1.8.1, Minecraft 1.8.2-pre1, Minecraft 1.8.2-pre2, Minecraft 1.8.2-pre3, Minecraft 1.8.2-pre4, Minecraft 1.8.2-pre5, Minecraft 1.8.2-pre6, Minecraft 1.8.3, Minecraft 1.8.4, Minecraft 1.8.5, Minecraft 1.8.6, Minecraft 1.8.7, Minecraft 1.8.8, Minecraft 15w31a, Minecraft 15w31b, Minecraft 15w31c, Minecraft 15w32a, Minecraft 15w32b, Minecraft 15w32c, Minecraft 15w33c, Minecraft 15w34a, Minecraft 15w34b, Minecraft 15w34c, Minecraft 15w34d, Minecraft 15w35b, Minecraft 15w35d, Minecraft 15w35e, Minecraft 15w36d, Minecraft 15w37a, Minecraft 15w38a, Minecraft 15w38b, Minecraft 15w39a, Minecraft 15w39b, Minecraft 15w39c, Minecraft 15w40a, Minecraft 15w40b, Minecraft 15w41b, Minecraft 15w42a, Minecraft 15w43a, Minecraft 15w43b, Minecraft 15w43c, Minecraft 15w44a, Minecraft 15w44b, Minecraft 15w45a, Minecraft 15w46a, Minecraft 15w47a, Minecraft 15w47c, Minecraft 15w49a, Minecraft 15w49b, Minecraft 1.8.9, Minecraft 15w50a, Minecraft 15w51a, Minecraft 15w51b, Minecraft 16w02a, Minecraft 16w03a, Minecraft 16w04a, Minecraft 16w05a, Minecraft 16w05b, Minecraft 16w06a, Minecraft 16w07a, Minecraft 16w07b, Minecraft 1.9 Pre-Release 1
    • Environment:

      All

    • Confirmation Status:
      Confirmed

      Description

      When a jukebox playing music is broken, the music initially stops, but when I opened up my inventory then closed it again, the song plays again. This works with any disc in creative and survival, and in both single- and multiplayer.

      1. Get Jukebox and disc of any kind
      2. Play the disc
      3. Break jukebox BEFORE song ends
      4. Open your inventory
      5. Close Inventory
      6. Song plays without a jukebox

      More generally, sounds can replay when a GUI is closed, when they shouldn't. This includes block breaking sounds, button sounds, and music. It also includes any GUI closing besides the pause menu in single player – your inventory, the chat GUI, or a chest all work.

      Debug info

      I recently figured out how to get minecraft to output sound debug logs by following this wiki.vg tutorial. Using the following configuration:

      <?xml version="1.0" encoding="UTF-8"?>
      <Configuration status="WARN" packages="net.minecraft,com.mojang">
          <Appenders>
              <Console name="SysOut" target="SYSTEM_OUT">
                  <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
              </Console>
          </Appenders>
          <Loggers>
              <Root level="debug">
                  <filters>
                      <MarkerFilter marker="SOUNDS" onMatch="ACCEPT" onMismatch="DENY" />
                  </filters>
                  <AppenderRef ref="SysOut"/>
              </Root>
          </Loggers> 
      </Configuration>
      

      This is what the logs look like when the bug is reproduced:

      [12:18:50] [Sound Library Loader/INFO]: Sound engine started
      [12:19:01] [Client thread/DEBUG]: Playing sound minecraft:sounds/random/click.ogg for event minecraft:gui.button.press as channel 231386e6-ecda-431b-a2e7-12f936caedb2
      [12:19:02] [Client thread/DEBUG]: Removed channel 231386e6-ecda-431b-a2e7-12f936caedb2 because it's not playing anymore
      [12:19:06] [Client thread/DEBUG]: Skipped playing sound minecraft:sounds/music/menu/menu2.ogg, volume was zero.
      [12:19:10] [Client thread/DEBUG]: Playing sound minecraft:sounds/random/click.ogg for event minecraft:gui.button.press as channel 229f3257-5902-4b42-bcae-eee3d53ec16c
      [12:19:11] [Client thread/DEBUG]: Removed channel 229f3257-5902-4b42-bcae-eee3d53ec16c because it's not playing anymore
      [12:19:22] [Client thread/DEBUG]: Skipped playing sound minecraft:sounds/music/game/calm2.ogg, volume was zero.
      [12:19:37] [Client thread/DEBUG]: Playing sound minecraft:sounds/step/stone4.ogg for event minecraft:step.stone as channel 07f5ef05-28e9-4d6d-8f31-d0a7df0fe69b
      [12:19:38] [Client thread/DEBUG]: Removed channel 07f5ef05-28e9-4d6d-8f31-d0a7df0fe69b because it's not playing anymore
      [12:19:38] [Client thread/DEBUG]: Playing sound minecraft:sounds/step/grass3.ogg for event minecraft:step.grass as channel 1a781d6d-ab09-425b-bb92-7f6009b42362
      [12:19:39] [Client thread/DEBUG]: Removed channel 1a781d6d-ab09-425b-bb92-7f6009b42362 because it's not playing anymore
      [12:19:45] [Client thread/DEBUG]: Playing sound minecraft:sounds/dig/stone1.ogg for event minecraft:dig.stone as channel c16bccae-f170-44fc-8082-f1567ddcd783
      [12:19:46] [Client thread/DEBUG]: Removed channel c16bccae-f170-44fc-8082-f1567ddcd783 because it's not playing anymore
      [12:19:47] [Client thread/DEBUG]: Playing sound minecraft:sounds/dig/stone1.ogg for event minecraft:dig.stone as channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Resuming channel 73f94183-6a17-4355-b356-2374cd71ed68
      [12:19:48] [Client thread/DEBUG]: Removed channel 73f94183-6a17-4355-b356-2374cd71ed68 because it's not playing anymore
      

      For 73f94183-6a17-4355-b356-2374cd71ed68, I was mashing open/close inventory (which is how the bug is reproduced). As you can see, it's getting resumed multiple times, despite never getting paused. It only replays the sound once (since resuming the sound is simply playing it again), but probably the sound is getting played before it is removed.

      Programmatic cause

      Note: MCP names for Minecraft 1.8 (mcp910) are used here. Obfuscated names as of 1.9-pre1 appear at the bottom.

      The reason that this bug occurs is that sounds are replayed whenever a GUI is closed. The most likely reason why sounds were replayed is that they are paused when certain GUIs open (such as the ingame pause menu) in single player. However, the sounds are played even if they were never paused (IE, they are played when any GUI is closed).

      net.minecraft.client.Minecraft, lines 964 - 1005
      /**
       * Sets the argument GuiScreen as the main (topmost visible) screen.
       */
      public void displayGuiScreen(GuiScreen guiScreenIn)
      {
          if (this.currentScreen != null)
          {
              this.currentScreen.onGuiClosed();
          }
      
          if (guiScreenIn == null && this.theWorld == null)
          {
              guiScreenIn = new GuiMainMenu();
          }
          else if (guiScreenIn == null && this.thePlayer.getHealth() <= 0.0F)
          {
              guiScreenIn = new GuiGameOver();
          }
      
          if (guiScreenIn instanceof GuiMainMenu)
          {
              this.gameSettings.showDebugInfo = false;
              this.ingameGUI.getChatGUI().clearChatMessages();
          }
      
          this.currentScreen = (GuiScreen)guiScreenIn;
      
          if (guiScreenIn != null)
          {
              this.setIngameNotInFocus();
              ScaledResolution var2 = new ScaledResolution(this, this.displayWidth, this.displayHeight);
              int var3 = var2.getScaledWidth();
              int var4 = var2.getScaledHeight();
              ((GuiScreen)guiScreenIn).setWorldAndResolution(this, var3, var4);
              this.skipRenderWorld = false;
          }
          else
          {
              this.mcSoundHandler.resumeSounds();
              this.setIngameFocus();
          }
      }
      

      When guiScreenIn is null, mcSoundHandler.resumeSounds is called. The SoundHandler methods simply call the same methods on the SoundManager.

      net.minecraft.client.audio.SoundManager, lines 467 - 495
      /**
       * Pauses all currently playing sounds
       */
      public void pauseAllSounds()
      {
          Iterator var1 = this.playingSounds.keySet().iterator();
      
          while (var1.hasNext())
          {
              String var2 = (String)var1.next();
              logger.debug(LOG_MARKER, "Pausing channel {}", new Object[] {var2});
              this.sndSystem.pause(var2);
          }
      }
      
      /**
       * Resumes playing all currently playing sounds (after pauseAllSounds)
       */
      public void resumeAllSounds()
      {
          Iterator var1 = this.playingSounds.keySet().iterator();
      
          while (var1.hasNext())
          {
              String var2 = (String)var1.next();
              logger.debug(LOG_MARKER, "Resuming channel {}", new Object[] {var2});
              this.sndSystem.play(var2);
          }
      }
      

      Note that resumeAllSounds works simply by calling play on all of the currently playing sounds. This works in most cases, as calling play while a sound is currently playing doesn't do anything. However, if the sound has finished playing but has not been cleaned up yet, calling play on it will cause it to play again, from the start. This is the core cause of this bug.

      The simplest way to fix it is to create a list of sounds that were paused when pauseAllSounds is called, and then only play the sounds in that list when resumeAllSounds is called (and then clear the list).

      Something like this:

      Replacement for code in net.minecraft.client.audio.SoundManager
      private List<String> pausedSounds = new ArrayList<String>();
      
      /**
       * Pauses all currently playing sounds
       */
      public void pauseAllSounds()
      {
          Iterator var1 = this.playingSounds.keySet().iterator();
      
          while (var1.hasNext())
          {
              String var2 = (String)var1.next();
              logger.debug(LOG_MARKER, "Pausing channel {}", new Object[] {var2});
              this.sndSystem.pause(var2);
              
              pausedSounds.add(var2);
          }
      }
      
      /**
       * Resumes playing all currently playing sounds (after pauseAllSounds)
       */
      public void resumeAllSounds()
      {
          Iterator var1 = this.pausedSounds.iterator();
      
          while (var1.hasNext())
          {
              String var2 = (String)var1.next();
              logger.debug(LOG_MARKER, "Resuming channel {}", new Object[] {var2});
              this.sndSystem.play(var2);
          }
          
          this.pausedSounds.clear();
      }
      

      (It may be necessary to make pauseAllSounds and resumeAllSounds synchronized depending on if it's called from a separate thread, but I'm guessing not since it isn't currently needed; also, don't blame me for the ugly syntax there; that's MCP's fault)

      I've tested this and found that it eliminates all of the looping sound issues, which is the main part of this bug.


      Here are the code snippets from above with 1.9-pre1's obfuscation (jd is used to decompile):

      The place where resumeSounds is called in displayGuiScreen:

      bce, lines 864 to 893
        public void a(bfa ☃) {
          if (this.m != null) {
            this.m.m();
          }
          
          if ((☃ == null) && (this.f == null)) {
            ☃ = new bfh();
          } else if ((☃ == null) && (this.h.bP() <= 0.0F)) {
            ☃ = new bel(null);
          }
          
          if (((☃ instanceof bfh)) || ((☃ instanceof bgq))) {
            this.u.aq = false;
            this.r.d().a();
          }
          
          this.m = ☃;
          
          if (☃ != null) {
            p();
            bcw ☃ = new bcw(this);
            int ☃ = ☃.a();
            int ☃ = ☃.b();
            ☃.a(this, ☃, ☃);
            this.s = false;
          } else {
            this.aL.e(); // This is resumeAllSounds, line 890
            o();
          }
        }
      

      Here is the code for resumeAllSounds and pauseAllSounds:

      byu, lines 409 to 421
        public void e() {
          for (String ☃ : this.i.keySet()) {
            b.debug(a, "Pausing channel {}", new Object[] { ☃ });
            this.f.pause(☃);
          }
        }
        
        public void f() {
          for (String ☃ : this.i.keySet()) {
            b.debug(a, "Resuming channel {}", new Object[] { ☃ });
            this.f.play(☃);
          }
        }
      

      And here are resumeAllSounds and pauseAllSounds with the bug fixed:

      byu
        List<String> pausedSounds = new ArrayList<String>();
        
        public void e() {
          for (String ☃ : this.i.keySet()) {
            b.debug(a, "Pausing channel {}", new Object[] { ☃ });
            this.f.pause(☃);
            pausedSounds.add(☃);
          }
        }
        
        public void f() {
          for (String ☃ : this.pausedSounds) {
            b.debug(a, "Resuming channel {}", new Object[] { ☃ });
            this.f.play(☃);
          }
      
          pausedSounds.clear();
        }
      

      Obfuscation as of earlier versions can be found here.

      There are other ways it could be fixed, but this is the simplest way.


      Some additional oddities with jukeboxes that aren't necessarily part of this (from MC-35896):
      a) In survival mode, ejecting a disc while breaking the jukebox block may make the jukebox music permanent (Goes off after exiting the world). (Both survival and creative mode). (separate bug?)
      b) Sometimes, when destroying the jukebox block (After a) ALL music can go off (Not any music until exiting Minecraft). (Creative mode).
      c) Sometimes, when playing jukebox exiting the world, jukebox music can stay on (Does not go off until exiting Minecraft). (Creative mode).

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                grum [Mojang] Grum (Erik Broes)
                Reporter:
                pokechu22 [Mod] Pokechu22
              • Votes:
                138 Vote for this issue
                Watchers:
                32 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved:
                  CHK: