IMIsle Modkit
Pages in this area

Playing sounds

There is no Lua function that plays a sound. A browser page can, because it is a normal web page with the HTML audio element. A client script loads a small hidden page and tells it what to play. The server can trigger that for one player or for all.

Sounds played this way are plain stereo. They have no position in the world and do not get quieter with distance.

The resource

resources/sounds/manifest.lua

name "sounds"
server "server.lua"
client "client.lua"
files { "ui/*" }

resources/sounds/ui/index.html

<!doctype html>
<meta charset="utf-8">
<style>html, body { margin: 0; background: transparent; }</style>
<script>
  const cache = {};
  modkit.events.add("play", (file, volume) => {
    const audio = cache[file] || (cache[file] = new Audio(file));
    audio.volume = Math.max(0, Math.min(1, volume));
    audio.currentTime = 0;
    audio.play().catch(error => modkit.trigger("soundBlocked", file, error.name));
  });
  modkit.events.add("stop", () => {
    for (const name in cache) { cache[name].pause(); }
  });
  modkit.trigger("soundsReady");
</script>

Put your sound files next to it, for example ui/horn.ogg and ui/rain.ogg. Use .ogg or .wav. The files { "ui/*" } line sends them to the clients.

resources/sounds/client.lua

local page
local ready = false
local waiting = {}

modkit.events.add("onClientReady", function()
  page = modkit.browser.add("ui/index.html")
  if not page then return end
  page:on("soundsReady", function()
    ready = true
    for _, item in ipairs(waiting) do page:call("play", item[1], item[2]) end
    waiting = {}
  end)
  page:on("soundBlocked", function(file, reason)
    modkit.log.warn("could not play " .. tostring(file) .. ": " .. tostring(reason))
  end)
end)

modkit.events.add("sounds:play", function(file, volume)
  if type(file) ~= "string" or file:find("..", 1, true) then return end
  volume = tonumber(volume) or 1
  if ready then page:call("play", file, volume) else waiting[#waiting + 1] = { file, volume } end
end)

modkit.events.add("sounds:stop", function()
  if ready then page:call("stop") end
end)

resources/sounds/server.lua

modkit.exports.register("play", function(player, file, volume)
  player:call("sounds:play", file, volume or 1)
end)

modkit.exports.register("playAll", function(file, volume)
  modkit.events.callAll("sounds:play", file, volume or 1)
end)

modkit.commands.add("horn", function()
  modkit.events.callAll("sounds:play", "horn.ogg", 0.8)
end, true)

modkit.events.add("zoneEnter", function(player, zone)
  if zone.name == "sanctuary" then player:call("sounds:play", "chime.ogg", 0.5) end
end)

Other resources play a sound with modkit.exports.sounds.playAll("horn.ogg").

Things to know

  • The page never becomes active (setActive is not called), so it takes no input and the player does not see it.
  • The browser is the Chromium build that ships with the game. Chromium decides through its autoplay policy whether a page may start audio without a click. When it refuses, play() fails with NotAllowedError, and the page above reports that to the client log as could not play <file>: NotAllowedError. A sound that follows a click inside an active page is always allowed.
  • File names are relative to the page, horn.ogg means ui/horn.ogg.
  • Only players who joined with the Isle Modkit launcher hear anything. Others have no client scripts.
  • Client scripts start some time after the join. A sound the server sends before that is lost, which is fine for effects and wrong for anything important.
  • Keep files small. Every player downloads the whole ui folder once after joining.
  • Looping music works the same way. Set audio.loop = true in the page and add a stop call.