--- howler.orig.js Fri May 23 11:52:20 2025
+++ howler.js Sun May 25 12:46:31 2025
@@ -1,11 +1,12 @@
-/*!
- * howler.js v2.2.3
- * howlerjs.com
+/*!
+ * @file howler.js v2.2.3 | howlerjs.com
+ * @version 2.2.3-planeptune
+ * @author James Simpson
+ * @copyright 2013-2020, James Simpson of GoldFire Studios | goldfirestudios.com
+ * @license MIT
*
- * (c) 2013-2020, James Simpson of GoldFire Studios
- * goldfirestudios.com
- *
- * MIT License
+ * This version of howler has been modified by zefie, for the Planeptune Loop Player ("Planeptune Edition"), and minified using uglify-js v3.x
+ * See https://loops.planeptune.org/geshi/source/js/howler.diff.txt for modifications to "Planeptune Edition"
*/
(function() {
@@ -29,6 +30,7 @@
*/
init: function() {
var self = this || Howler;
+ console.log('howler v2.2.3-planeptune initialized');
// Create a global ID counter.
self._counter = 1000;
@@ -61,6 +63,37 @@
return self;
},
+ /**
+ * Check if this browser is Internet Explorer.
+ * Returns the version if so, otherwise returns false
+ */
+
+ detectIE: function () {
+ var ua = window.navigator.userAgent;
+
+ var msie = ua.indexOf('MSIE ');
+ if (msie > 0) {
+ // IE 10 or older => return version number
+ return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
+ }
+
+ var trident = ua.indexOf('Trident/');
+ if (trident > 0) {
+ // IE 11 => return version number
+ var rv = ua.indexOf('rv:');
+ return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
+ }
+
+ var edge = ua.indexOf('Edge/');
+ if (edge > 0) {
+ // Edge (IE 12+) => return version number
+ return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
+ }
+
+ // other browser
+ return false;
+ },
+
/**
* Get/set the global volume for all sounds.
* @param {Float} vol Volume from 0.0 to 1.0.
@@ -84,7 +117,7 @@
}
// When using Web Audio, we just need to adjust the master gain.
- if (self.usingWebAudio) {
+ if (self._webAudio && !self._html5) {
self.masterGain.gain.setValueAtTime(vol, Howler.ctx.currentTime);
}
@@ -126,7 +159,7 @@
self._muted = muted;
// With Web Audio, we just need to mute the master gain.
- if (self.usingWebAudio) {
+ if (self._webAudio) {
self.masterGain.gain.setValueAtTime(muted ? 0 : self._volume, Howler.ctx.currentTime);
}
@@ -176,7 +209,7 @@
}
// Create a new AudioContext to make sure it is fully reset.
- if (self.usingWebAudio && self.ctx && typeof self.ctx.close !== 'undefined') {
+ if (self._webAudio && self.ctx && typeof self.ctx.close !== 'undefined') {
self.ctx.close();
self.ctx = null;
setupAudioContext();
@@ -208,7 +241,7 @@
self._autoSuspend();
// Check if audio is available.
- if (!self.usingWebAudio) {
+ if (!self._webAudio) {
// No audio is available on this system if noAudio is set to true.
if (typeof Audio !== 'undefined') {
try {
@@ -557,8 +590,8 @@
var self = this;
// Throw an error if no source is provided.
- if (!o.src || o.src.length === 0) {
- console.error('An array of source files must be passed with any new Howl.');
+ if (((!o.src || o.src.length === 0) && (!o.arraybuffer || !(o.arraybuffer instanceof ArrayBuffer) || !o.contenttype)) || (o.html5 && !o.src)) {
+ console.error('An array of source files, or an ArrayBuffer+ContentType, must be passed with any new Howl. Also, ArrayBuffer+ContentType are incompatible with html5 mode.');
return;
}
@@ -588,7 +621,11 @@
self._preload = (typeof o.preload === 'boolean' || o.preload === 'metadata') ? o.preload : true;
self._rate = o.rate || 1;
self._sprite = o.sprite || {};
+ self._buffer = o.arraybuffer || null;
+ self._contenttype = o.contenttype || null;
self._src = (typeof o.src !== 'string') ? o.src : [o.src];
+ self._nextsprite = o.nextsprite || null;
+ self._timeroffset = o.timeroffset || 30;
self._volume = o.volume !== undefined ? o.volume : 1;
self._xhr = {
method: o.xhr && o.xhr.method ? o.xhr.method : 'GET',
@@ -598,6 +635,8 @@
// Setup all other default properties.
self._duration = 0;
+ self._decoded = null;
+ self._hires = null;
self._state = 'unloaded';
self._sounds = [];
self._endTimers = {};
@@ -610,6 +649,7 @@
self._onload = o.onload ? [{fn: o.onload}] : [];
self._onloaderror = o.onloaderror ? [{fn: o.onloaderror}] : [];
self._onplayerror = o.onplayerror ? [{fn: o.onplayerror}] : [];
+ self._onspritechange = o.onspritechange ? [{fn: o.onspritechange}] : [];
self._onpause = o.onpause ? [{fn: o.onpause}] : [];
self._onplay = o.onplay ? [{fn: o.onplay}] : [];
self._onstop = o.onstop ? [{fn: o.onstop}] : [];
@@ -662,7 +702,14 @@
self._emit('loaderror', null, 'No audio support.');
return;
}
-
+
+ if (self._buffer instanceof ArrayBuffer && !self._html5) {
+ if (!self._src) {
+ url = ['internal_buffer'];
+ self._src = url;
+ }
+ }
+
// Make sure our source is in an array.
if (typeof self._src === 'string') {
self._src = [self._src];
@@ -684,7 +731,11 @@
}
// Extract the file extension from the URL or base64 data URI.
- ext = /^data:audio\/([^;,]+);/i.exec(str);
+ if (self._contenttype) {
+ ext = self._contenttype.split("/")[1];
+ } else {
+ ext = /^data:audio\/([^;,]+);/i.exec(str);
+ }
if (!ext) {
ext = /\.([^.]+)$/.exec(str.split('?', 1)[0]);
}
@@ -869,10 +920,15 @@
} else {
sound._loop ? node.bufferSource.start(0, seek, 86400) : node.bufferSource.start(0, seek, duration);
}
+
+ if (Howler.detectIE() == 11) {
+ // IE11 Workaround where rate gets reset
+ node.playbackRate = sound._rate;
+ }
// Start a new timer if none is present.
if (timeout !== Infinity) {
- self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
+ self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));
}
if (!internal) {
@@ -1563,6 +1619,10 @@
var seek = self.seek(id[i]);
var duration = ((self._sprite[sound._sprite][0] + self._sprite[sound._sprite][1]) / 1000) - seek;
var timeout = (duration * 1000) / Math.abs(sound._rate);
+
+ if (self._webAudio) {
+ timeout = timeout - self._timeroffset;
+ }
// Start a new end timer if sound is already playing.
if (self._endTimers[id[i]] || !sound._paused) {
@@ -1639,14 +1699,44 @@
if (typeof seek === 'number' && seek >= 0) {
// Pause the sound and update position for restarting playback.
var playing = self.playing(id);
- if (playing) {
- self.pause(id, true);
+ var sprite = sound._sprite;
+ sound._rateSeek = 0;
+
+ var soundDuration = ((self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000);
+ if (seek > soundDuration) {
+ seek = 0;
+ }
+
+ if (playing && !self._webAudio) {
+ self.pause(id, true);
}
// Move the position of the track and cancel timer.
sound._seek = seek;
sound._ended = false;
self._clearTimer(id);
+
+
+ // Restart the playback if the sound was playing.
+ if (playing && self._webAudio) {
+ var oldbuffer = sound._node.bufferSource;
+ self._refreshBuffer(sound);
+ sound._playStart = Howler.ctx.currentTime;
+ if (typeof sound._node.bufferSource.start === 'undefined') {
+ sound._node.bufferSource.noteGrainOn(0, seek);
+ } else {
+ sound._node.bufferSource.start(0, seek);
+ }
+ if (oldbuffer) {
+ oldbuffer.stop();
+ oldbuffer = null;
+ }
+ var duration = soundDuration - seek;
+ var timeout = (duration * 1000) / Math.abs(sound._rate);
+ if (timeout !== Infinity) {
+ self._endTimers[id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));
+ }
+ }
// Update the seek position for HTML5 Audio.
if (!self._webAudio && sound._node && !isNaN(sound._node.duration)) {
@@ -1683,6 +1773,10 @@
return sound._seek + (rateSeek + realTime * Math.abs(sound._rate));
} else {
return sound._node.currentTime;
+ // Workaround for IE11 reseting the rate
+ if (Howler.detectIE() == 11) {
+ self.rate(sound._rate,id);
+ }
}
}
}
@@ -1944,6 +2038,17 @@
},
/**
+ * Fired when playback is switched to another sprite with nextsprite
+ * @param {Sound} sound The sound object to work with.
+ * @return {Howl}
+ */
+
+ _onspritechange: function (sound) {
+ var self = this;
+ return self;
+ },
+
+ /**
* Fired when playback ends at the end of the duration.
* @param {Sound} sound The sound object to work with.
* @return {Howl}
@@ -1968,18 +2073,57 @@
// Restart the playback for HTML5 Audio loop.
if (!self._webAudio && loop) {
- self.stop(sound._id, true).play(sound._id);
+ if (self._nextsprite && sound._sprite != self._nextsprite) {
+ sprite = self._nextsprite;
+ self._nextsprite = null;
+ sound._sprite = sprite; // this line may be redundant
+ self.stop(sound._id, true).play(sprite);
+ self._emit('spritechange',sound);
+ } else {
+ self.stop(sound._id, true).play(sound._id);
+ }
}
// Restart this timer if on a Web Audio loop.
if (self._webAudio && loop) {
- self._emit('play', sound._id);
- sound._seek = sound._start || 0;
- sound._rateSeek = 0;
- sound._playStart = Howler.ctx.currentTime;
-
- var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate);
- self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), timeout);
+ var currpos = Math.round(self.seek() * 1000);
+ var wanted = self._sprite[sprite][0] + self._sprite[sprite][1];
+ var offset = (currpos - wanted);
+ self._clearTimer(sound._id);
+ if (offset < -6) {
+ // "hi-res" mode
+ if (!self._hires) {
+ console.log("entering hi-res emulation mode");
+ self._hires = setInterval(self._ended.bind(self, sound), 1);
+ }
+ return false;
+ }
+
+ if (self._hires) {
+ console.log("exiting hi-res emulation mode");
+ clearInterval(self._hires)
+ self._hires = null;
+ }
+ console.log("howler: end timer fired. playtime was "+currpos+"ms, expected "+wanted+"ms ("+offset+"ms off)");
+ sound._rateSeek = 0;
+ sound._playStart = Howler.ctx.currentTime;
+ if (self._nextsprite && sound._sprite != self._nextsprite) {
+ // manually change sprite
+ sprite = self._nextsprite;
+ self._nextsprite = null;
+ sound._sprite = sprite;
+ self._emit('spritechange',sound);
+ sound._start = self._sprite[sprite][0] / 1000;
+ sound._stop = (self._sprite[sprite][0] + self._sprite[sprite][1]) / 1000;
+ sound._loop = loop;
+ sound._seek = sound._start || 0;
+ self.seek(sound._seek,sound._id);
+ } else {
+ self._emit('play', sound._id);
+ sound._seek = sound._start || 0;
+ var timeout = ((sound._stop - sound._start) * 1000) / Math.abs(sound._rate);
+ self._endTimers[sound._id] = setTimeout(self._ended.bind(self, sound), (timeout - self._timeroffset));
+ }
}
// Mark the node as paused.
@@ -2378,6 +2522,11 @@
*/
var loadBuffer = function(self) {
var url = self._src;
+
+ if (self._buffer instanceof ArrayBuffer && !self._html5) {
+ decodeAudioData(self._buffer, self);
+ return;
+ }
// Check if the buffer has already been cached and use it instead.
if (cache[url]) {
@@ -2472,9 +2621,9 @@
// Decode the buffer into an audio source.
if (typeof Promise !== 'undefined' && Howler.ctx.decodeAudioData.length === 1) {
- Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error);
+ Howler.ctx.decodeAudioData(arraybuffer.slice(0)).then(success).catch(error);
} else {
- Howler.ctx.decodeAudioData(arraybuffer, success, error);
+ Howler.ctx.decodeAudioData(arraybuffer.slice(0), success, error);
}
}
@@ -2496,6 +2645,7 @@
// Fire the loaded event.
if (self._state !== 'loaded') {
+ self._decoded = buffer;
self._state = 'loaded';
self._emit('load');
self._loadQueue();