@makefully/platypus 4.1.5 → 4.1.8
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/CHANGELOG.md +31 -0
- package/README.md +3 -3
- package/lib/platypus.js +1 -1
- package/package.json +7 -6
- package/src/AABB.js +685 -0
- package/src/ActionState.js +121 -0
- package/src/AssetManager.js +206 -0
- package/src/CollisionData.js +96 -0
- package/src/CollisionDataContainer.js +127 -0
- package/src/CollisionShape.js +315 -0
- package/src/Component.js +231 -0
- package/src/Data.js +64 -0
- package/src/DataMap.js +204 -0
- package/src/Entity.js +404 -0
- package/src/EntityLinker.js +113 -0
- package/src/EventHandler.js +115 -0
- package/src/EventHandlerList.js +251 -0
- package/src/Game.js +840 -0
- package/src/Gamepad.js +175 -0
- package/src/ID3CaptionPlayer.js +61 -0
- package/src/Messenger.js +365 -0
- package/src/PIXIAnimation.js +622 -0
- package/src/RandomSet.js +140 -0
- package/src/SFXPlayer.js +120 -0
- package/src/StateMap.js +252 -0
- package/src/Storage.js +152 -0
- package/src/TickerClient.js +48 -0
- package/src/TickerServer.js +23 -0
- package/src/TimeEvent.js +50 -0
- package/src/TimeEventList.js +100 -0
- package/src/VOPlayer.js +684 -0
- package/src/Vector.js +702 -0
- package/src/components/AIChaser.js +161 -0
- package/src/components/AIPacer.js +107 -0
- package/src/components/AudioMusic.js +151 -0
- package/src/components/AudioSFX.js +673 -0
- package/src/components/AudioVO.js +301 -0
- package/src/components/CollisionBasic.js +826 -0
- package/src/components/CollisionFilter.js +74 -0
- package/src/components/CollisionGroup.js +448 -0
- package/src/components/CollisionTiles.js +608 -0
- package/src/components/ComponentSwitcher.js +173 -0
- package/src/components/ControllerInput.js +65 -0
- package/src/components/Counter.js +165 -0
- package/src/components/EntityCamera.js +215 -0
- package/src/components/EntityContainer.js +549 -0
- package/src/components/EntityController.js +541 -0
- package/src/components/HandlerCamera.js +1052 -0
- package/src/components/HandlerCollision.js +1318 -0
- package/src/components/HandlerController.js +291 -0
- package/src/components/HandlerLogic.js +394 -0
- package/src/components/HandlerRender.js +217 -0
- package/src/components/Interactive.js +502 -0
- package/src/components/LevelBuilder.js +524 -0
- package/src/components/LogicAngularMovement.js +154 -0
- package/src/components/LogicAttachment.js +265 -0
- package/src/components/LogicButton.js +466 -0
- package/src/components/LogicCarrier.js +39 -0
- package/src/components/LogicDestroyMe.js +58 -0
- package/src/components/LogicDirectionalMovement.js +325 -0
- package/src/components/LogicDragDrop.js +371 -0
- package/src/components/LogicImpactLaunch.js +144 -0
- package/src/components/LogicPacingPlatform.js +64 -0
- package/src/components/LogicPortable.js +112 -0
- package/src/components/LogicPortal.js +115 -0
- package/src/components/LogicPushable.js +98 -0
- package/src/components/LogicRebounder.js +136 -0
- package/src/components/LogicRegionSpawner.js +139 -0
- package/src/components/LogicRotationalMovement.js +205 -0
- package/src/components/LogicSpawner.js +159 -0
- package/src/components/LogicStateMachine.js +382 -0
- package/src/components/LogicSwitch.js +77 -0
- package/src/components/LogicTeleportee.js +68 -0
- package/src/components/LogicTeleporter.js +93 -0
- package/src/components/LogicTimer.js +178 -0
- package/src/components/LogicWindUpRacer.js +179 -0
- package/src/components/Motion.js +371 -0
- package/src/components/Mover.js +566 -0
- package/src/components/Node.js +289 -0
- package/src/components/NodeMap.js +295 -0
- package/src/components/NodeResident.js +636 -0
- package/src/components/Orientation.js +697 -0
- package/src/components/RandomEvents.js +73 -0
- package/src/components/RelativePosition.js +204 -0
- package/src/components/RelayFamily.js +106 -0
- package/src/components/RelayGame.js +53 -0
- package/src/components/RelayLinker.js +94 -0
- package/src/components/RelayParent.js +57 -0
- package/src/components/RelaySelf.js +55 -0
- package/src/components/Render.js +178 -0
- package/src/components/RenderAnimator.js +275 -0
- package/src/components/RenderContainer.js +646 -0
- package/src/components/RenderDebug.js +319 -0
- package/src/components/RenderDestroyMe.js +59 -0
- package/src/components/RenderLottie.js +149 -0
- package/src/components/RenderProgress.js +120 -0
- package/src/components/RenderSpine.js +840 -0
- package/src/components/RenderSprite.js +527 -0
- package/src/components/RenderText.js +147 -0
- package/src/components/RenderTiles.js +1440 -0
- package/src/components/SceneChanger.js +128 -0
- package/src/components/TiledLoader.js +1459 -0
- package/src/components/Timeline.js +314 -0
- package/src/components/Tutorial.js +267 -0
- package/src/components/Tween.js +296 -0
- package/src/components/VoiceOver.js +373 -0
- package/src/components/XHR.js +129 -0
- package/src/components/index.js +79 -0
- package/src/factory.js +305 -0
- package/src/index.js +159 -0
- package/src/mixins/PIXI.Graphics.js +41 -0
- package/src/platypus.css +68 -0
- package/src/sayHello.js +50 -0
- package/src/shapes/Capsule.js +74 -0
- package/src/shapes/Circle.js +43 -0
- package/src/shapes/Ellipse.js +117 -0
- package/src/shapes/Point.js +191 -0
- package/src/shapes/Polygon.js +112 -0
- package/src/shapes/Polyline.js +97 -0
- package/src/shapes/Rectangle.js +78 -0
- package/src/shapes/RoundedRectangle.js +33 -0
- package/src/shapes/Segment.js +346 -0
- package/src/shapes/adjustedXY.js +19 -0
- package/src/shapes/castFromBox2D.js +92 -0
- package/src/shapes/castFromObject.js +53 -0
- package/src/shapes/castFromPixiGraphics.js +6 -0
- package/src/shapes/castFromPixiShape.js +89 -0
- package/src/shapes/castToBox2D.js +190 -0
- package/src/shapes/castToPixiGraphics.js +100 -0
- package/src/shapes/castToPixiShape.js +61 -0
- package/src/shapes/checkPointsMap.js +313 -0
- package/src/shapes/index.js +35 -0
- package/src/utils/array.js +73 -0
- package/src/utils/string.js +39 -0
- package/lib/243.platypus.mjs +0 -1
- package/lib/platypus.mjs +0 -1
package/src/VOPlayer.js
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/* global platypus */
|
|
2
|
+
import {arrayCache, greenSlice, greenSplice} from './utils/array.js';
|
|
3
|
+
import Data from './Data.js';
|
|
4
|
+
import Messenger from './Messenger.js';
|
|
5
|
+
import TimeEventList from './TimeEventList.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This class is used to create `platypus.game.voPlayer` and manages playback by only playing one at a time, playing a list, and even handling captions at the same time.
|
|
9
|
+
*
|
|
10
|
+
* This class borrows heavily from SpringRoll v1 to match the original capabilities exposed for Platypus v1.
|
|
11
|
+
*
|
|
12
|
+
* @memberof platypus
|
|
13
|
+
* @class VOPlayer
|
|
14
|
+
* @extends platypus.Messenger
|
|
15
|
+
* @param {Game} game The game instance for which to play audio.
|
|
16
|
+
* @param {assetManager} assetCache The Platypus assetManager used to load and unload VO clips.
|
|
17
|
+
*/
|
|
18
|
+
class VOPlayer extends Messenger {
|
|
19
|
+
constructor (game, assetCache) {
|
|
20
|
+
super();
|
|
21
|
+
|
|
22
|
+
this.game = game;
|
|
23
|
+
this.assetCache = assetCache;
|
|
24
|
+
this.tagReader = game.options.modules?.jsmediatags ?? null;
|
|
25
|
+
|
|
26
|
+
//Bound method calls
|
|
27
|
+
this._onSoundFinished = this._onSoundFinished.bind(this);
|
|
28
|
+
this._updateSilence = this._updateSilence.bind(this);
|
|
29
|
+
this._syncCaptionToSound = this._syncCaptionToSound.bind(this);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Preloading next sound.
|
|
33
|
+
* @property {String} preloadingNextSound
|
|
34
|
+
* @private
|
|
35
|
+
*/
|
|
36
|
+
this.preloadingNextSound = '';
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* If the sound is currently paused. Setting this has no effect - use pause()
|
|
40
|
+
* and resume().
|
|
41
|
+
* @property {Boolean} paused
|
|
42
|
+
* @public
|
|
43
|
+
* @readOnly
|
|
44
|
+
*/
|
|
45
|
+
this.paused = false;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The current list of audio/silence times/functions.
|
|
49
|
+
* Generally you will not need to modify this.
|
|
50
|
+
* @property {Array} voList
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
this.voList = null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The current position in voList.
|
|
57
|
+
* @property {int} _listCounter
|
|
58
|
+
* @private
|
|
59
|
+
*/
|
|
60
|
+
this._listCounter = 0;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The current audio alias being played.
|
|
64
|
+
* @property {String} _currentVO
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
this._currentVO = null;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The current audio instance being played.
|
|
71
|
+
* @property {SoundInstance} _soundInstance
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
this._soundInstance = null;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The callback for when the list is finished.
|
|
78
|
+
* @property {Function} _callback
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
this._callback = null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The callback for when the list is interrupted for any reason.
|
|
85
|
+
* @property {Function} _cancelledCallback
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
this._cancelledCallback = null;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A timer for silence entries in the list, in milliseconds.
|
|
92
|
+
* @property {int} _timer
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
this._timer = 0;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The captions object
|
|
99
|
+
* @property {springroll.Captions} _captions
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
this._captions = null;
|
|
103
|
+
|
|
104
|
+
this.masterVolume = 1;
|
|
105
|
+
this.volume = 1;
|
|
106
|
+
this.captionMute = true;
|
|
107
|
+
this.currentlyLoadingAudio = false;
|
|
108
|
+
this.interruptable = true;
|
|
109
|
+
this.playQueue = arrayCache.setUp();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* If VOPlayer is currently playing (audio or silence).
|
|
114
|
+
* @property {Boolean} playing
|
|
115
|
+
* @public
|
|
116
|
+
* @readOnly
|
|
117
|
+
*/
|
|
118
|
+
get playing () {
|
|
119
|
+
return this._currentVO !== null || this._timer > 0;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The current VO alias that is playing, even if it is just a caption. If a silence timer
|
|
124
|
+
* is running, currentVO will be null.
|
|
125
|
+
* @property {Boolean} currentVO
|
|
126
|
+
* @public
|
|
127
|
+
* @readOnly
|
|
128
|
+
*/
|
|
129
|
+
get currentVO () {
|
|
130
|
+
return this._currentVO;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The springroll.Captions object used for captions. The developer is responsible for initializing this with a captions dictionary config file and a reference to a text field.
|
|
135
|
+
* @property {Captions} captions
|
|
136
|
+
* @public
|
|
137
|
+
*/
|
|
138
|
+
set captions (captions) {
|
|
139
|
+
this._captions = captions;
|
|
140
|
+
if (captions) {
|
|
141
|
+
captions.selfUpdate = false;
|
|
142
|
+
this.setCaptionMute(this.captionMute);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get captions () {
|
|
147
|
+
return this._captions;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* The amount of time elapsed in the currently playing item of audio/silence in milliseconds
|
|
152
|
+
* @property {int} currentPosition
|
|
153
|
+
*/
|
|
154
|
+
get currentPosition () {
|
|
155
|
+
if (!this.playing) return 0;
|
|
156
|
+
//active audio
|
|
157
|
+
if (this._soundInstance)
|
|
158
|
+
return this._soundInstance.position;
|
|
159
|
+
//captions only
|
|
160
|
+
else if (this._currentVO)
|
|
161
|
+
return this._timer;
|
|
162
|
+
//silence timer
|
|
163
|
+
else
|
|
164
|
+
return this.voList[this._listCounter] - this._timer;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The duration of the currently playing item of audio/silence in milliseconds. If this is waiting on an audio file to load for the first time, it will be 0, as there is no duration data to give.
|
|
169
|
+
* @property {int} currentDuration
|
|
170
|
+
*/
|
|
171
|
+
get currentDuration () {
|
|
172
|
+
if (!this.playing) {
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
//active audio
|
|
177
|
+
if (this._soundInstance) {
|
|
178
|
+
return this._soundInstance.duration;
|
|
179
|
+
} else if (this._currentVO && this._captions) { //captions only
|
|
180
|
+
return this._captions.currentDuration;
|
|
181
|
+
} else { //silence timer
|
|
182
|
+
return this.voList[this._listCounter];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Calculates the amount of time elapsed in the current playlist of audio/silence.
|
|
188
|
+
* @method platypus.VOPlayer#getElapsed
|
|
189
|
+
* @return {int} The elapsed time in milliseconds.
|
|
190
|
+
*/
|
|
191
|
+
getElapsed () {
|
|
192
|
+
const
|
|
193
|
+
{assetCache} = platypus;
|
|
194
|
+
let index = 0,
|
|
195
|
+
total = 0;
|
|
196
|
+
|
|
197
|
+
if (!this.voList)
|
|
198
|
+
{
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < this._listCounter; ++i) {
|
|
203
|
+
const item = this.voList[i];
|
|
204
|
+
if (typeof item === "string") {
|
|
205
|
+
total += assetCache.get(assetCache.getFileId(item)).duration * 1000;
|
|
206
|
+
}
|
|
207
|
+
else if (typeof item === "number")
|
|
208
|
+
{
|
|
209
|
+
total += item;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
//get the current item
|
|
214
|
+
index = this._listCounter;
|
|
215
|
+
if (index < this.voList.length) {
|
|
216
|
+
const item = this.voList[index];
|
|
217
|
+
if (typeof item === "string") {
|
|
218
|
+
if (this._soundInstance) {
|
|
219
|
+
total += this._soundInstance._elapsed * 1000;
|
|
220
|
+
} // Otherwise it's not yet loaded so progress is `0`
|
|
221
|
+
} else if (typeof item === "number") {
|
|
222
|
+
total += item - this._timer;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return total;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Pauses the current VO, caption, or silence timer if the VOPlayer is playing.
|
|
230
|
+
* @method platypus.VOPlayer#pause
|
|
231
|
+
* @public
|
|
232
|
+
*/
|
|
233
|
+
pause () {
|
|
234
|
+
if (this.paused || !this.playing) return;
|
|
235
|
+
|
|
236
|
+
this.paused = true;
|
|
237
|
+
|
|
238
|
+
if (this._soundInstance)
|
|
239
|
+
this._soundInstance.pause();
|
|
240
|
+
|
|
241
|
+
//remove any update callback
|
|
242
|
+
this.game.off("tick", this._syncCaptionToSound);
|
|
243
|
+
this.game.off("tick", this._updateSilence);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Resumes the current VO, caption, or silence timer if the VOPlayer was paused.
|
|
248
|
+
* @method platypus.VOPlayer#resume
|
|
249
|
+
* @public
|
|
250
|
+
* @listens platypus.Game#tick
|
|
251
|
+
*/
|
|
252
|
+
resume () {
|
|
253
|
+
if (!this.paused) return;
|
|
254
|
+
|
|
255
|
+
this.paused = false;
|
|
256
|
+
if (this._soundInstance)
|
|
257
|
+
this._soundInstance.resume();
|
|
258
|
+
//captions for solo captions or VO
|
|
259
|
+
if (this._captions.activeCaption) {
|
|
260
|
+
if (this._soundInstance) {
|
|
261
|
+
this.game.on("tick", this._syncCaptionToSound);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
//timer
|
|
265
|
+
} else {
|
|
266
|
+
this.game.on("tick", this._updateSilence);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Plays a single audio alias, interrupting any current playback.
|
|
272
|
+
* Alternatively, plays a list of audio files, timers, and/or functions.
|
|
273
|
+
* Audio in the list will be preloaded to minimize pauses for loading.
|
|
274
|
+
* @method platypus.VOPlayer#play
|
|
275
|
+
* @public
|
|
276
|
+
* @param {String|Array} idOrList The alias of the audio file to play or the
|
|
277
|
+
* array of items to play/call in order.
|
|
278
|
+
* @param {Function} [callback] The function to call when playback is complete.
|
|
279
|
+
* @param {Function|Boolean} [cancelledCallback] The function to call when playback
|
|
280
|
+
* is interrupted with a stop() or play() call. If this value is a boolean
|
|
281
|
+
* <code>true</code> then callback will be used instead.
|
|
282
|
+
*/
|
|
283
|
+
play (idOrList, callback, cancelledCallback, interruptable = true) {
|
|
284
|
+
if (!this.startingNewTrack) {
|
|
285
|
+
if (this.currentlyLoadingAudio || (!this.interruptable && this.playing)) {
|
|
286
|
+
this.playQueue.push(Data.setUp(
|
|
287
|
+
'idOrList', idOrList,
|
|
288
|
+
'callback', callback,
|
|
289
|
+
'cancelledCallback', cancelledCallback,
|
|
290
|
+
'interruptable', interruptable
|
|
291
|
+
));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this.startingNewTrack = true;
|
|
295
|
+
this.stop();
|
|
296
|
+
this.startingNewTrack = false;
|
|
297
|
+
|
|
298
|
+
this.interruptable = interruptable;
|
|
299
|
+
|
|
300
|
+
this._listCounter = -1;
|
|
301
|
+
if (typeof idOrList === "string") {
|
|
302
|
+
this.voList = arrayCache.setUp(0, idOrList);
|
|
303
|
+
} else {
|
|
304
|
+
this.voList = greenSlice(idOrList);
|
|
305
|
+
this.voList.unshift(0);
|
|
306
|
+
}
|
|
307
|
+
this._callback = callback;
|
|
308
|
+
this._cancelledCallback = cancelledCallback === true ? callback : cancelledCallback;
|
|
309
|
+
this._onSoundFinished();
|
|
310
|
+
|
|
311
|
+
} else {
|
|
312
|
+
platypus.debug.warn('VOPlayer: Tried playing a new track while a new track was starting up.');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Callback for when audio/timer is finished to advance to the next item in the list.
|
|
318
|
+
* @private
|
|
319
|
+
*/
|
|
320
|
+
_onSoundFinished (audio) {
|
|
321
|
+
if (this._listCounter >= 0) {
|
|
322
|
+
const currentVO = this._currentVO;
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Fired when a new VO, caption, or silence timer completes
|
|
326
|
+
* @event platypus.VOPlayer#end
|
|
327
|
+
* @param {String} currentVO The alias of the VO or caption that has begun, or null if it is a silence timer.
|
|
328
|
+
*/
|
|
329
|
+
this.trigger("end", currentVO);
|
|
330
|
+
if (typeof currentVO === "string") {
|
|
331
|
+
this.voList[0] += (audio?.duration ?? 0) * 1000;
|
|
332
|
+
this.unloadSound(currentVO);
|
|
333
|
+
greenSplice(this.voList, 1);
|
|
334
|
+
this._listCounter -= 1;
|
|
335
|
+
} else if (typeof this._currentVO === "function") {
|
|
336
|
+
greenSplice(this.voList, 1);
|
|
337
|
+
this._listCounter -= 1;
|
|
338
|
+
} else {
|
|
339
|
+
this.voList[0] += this.voList[1];
|
|
340
|
+
greenSplice(this.voList, 1);
|
|
341
|
+
this._listCounter -= 1;
|
|
342
|
+
}
|
|
343
|
+
} else {
|
|
344
|
+
this._listCounter = 0; // bump past elapsed storage index.
|
|
345
|
+
}
|
|
346
|
+
//remove any update callback
|
|
347
|
+
this.game.off("tick", this._syncCaptionToSound);
|
|
348
|
+
this.game.off("tick", this._updateSilence);
|
|
349
|
+
|
|
350
|
+
//if we have captions and an audio instance, set the caption time to the length of the audio
|
|
351
|
+
if (this._captions && this._captions.activeCaption && this._soundInstance) {
|
|
352
|
+
const activeCaption = this._captions.activeCaption;
|
|
353
|
+
|
|
354
|
+
activeCaption.lineIndex = activeCaption.lines.length;
|
|
355
|
+
}
|
|
356
|
+
this._soundInstance = null; //clear the audio instance
|
|
357
|
+
this._listCounter++; //advance list
|
|
358
|
+
|
|
359
|
+
//if the list is complete
|
|
360
|
+
if (this._listCounter >= this.voList.length) {
|
|
361
|
+
const c = this._callback;
|
|
362
|
+
|
|
363
|
+
if (this._captions) {
|
|
364
|
+
this._captions.stop();
|
|
365
|
+
}
|
|
366
|
+
this._currentVO = null;
|
|
367
|
+
this._cancelledCallback = null;
|
|
368
|
+
this._callback = null;
|
|
369
|
+
arrayCache.recycle(this.voList);
|
|
370
|
+
this.voList = null;
|
|
371
|
+
if (c) {
|
|
372
|
+
c();
|
|
373
|
+
}
|
|
374
|
+
if (this.interruptable) {
|
|
375
|
+
this.clearQueue();
|
|
376
|
+
} else {
|
|
377
|
+
this.checkQueue();
|
|
378
|
+
}
|
|
379
|
+
} else {
|
|
380
|
+
/**
|
|
381
|
+
* Fired when a new VO, caption, or silence timer begins
|
|
382
|
+
* @event platypus.VOPlayer#start
|
|
383
|
+
* @param {String} currentVO The alias of the VO or caption that has begun, or null if it is a silence timer.
|
|
384
|
+
*/
|
|
385
|
+
this._currentVO = this.voList[this._listCounter];
|
|
386
|
+
if (typeof this._currentVO === "string") {
|
|
387
|
+
//If the sound doesn't exist, then we play it and let it fail,
|
|
388
|
+
//an error should be shown and playback will continue
|
|
389
|
+
this._playSound();
|
|
390
|
+
this.trigger("start", this._currentVO);
|
|
391
|
+
} else if (typeof this._currentVO === "function") {
|
|
392
|
+
this._currentVO(); //call function
|
|
393
|
+
this._onSoundFinished(); //immediately continue
|
|
394
|
+
} else {
|
|
395
|
+
this._timer = this._currentVO; //set up a timer to wait
|
|
396
|
+
this._currentVO = null;
|
|
397
|
+
this.game.on("tick", this._updateSilence);
|
|
398
|
+
this.trigger("start", null);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The update callback used for silence timers.
|
|
405
|
+
* This method is bound to the VOPlayer instance.
|
|
406
|
+
* @private
|
|
407
|
+
* @param {int} elapsed The time elapsed since the previous frame, in milliseconds.
|
|
408
|
+
*/
|
|
409
|
+
_updateSilence (tick) {
|
|
410
|
+
this._timer -= tick.delta;
|
|
411
|
+
|
|
412
|
+
if (this._timer <= 0) {
|
|
413
|
+
this._onSoundFinished();
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* The update callback used for updating captions with active audio.
|
|
419
|
+
* This method is bound to the VOPlayer instance.
|
|
420
|
+
* @private
|
|
421
|
+
* @param {int} elapsed The time elapsed since the previous frame, in milliseconds.
|
|
422
|
+
*/
|
|
423
|
+
_syncCaptionToSound (tick) {
|
|
424
|
+
if (!this._soundInstance) return;
|
|
425
|
+
|
|
426
|
+
const
|
|
427
|
+
{_soundInstance, audioEventQueue} = this,
|
|
428
|
+
elapsed = _soundInstance._elapsed * 1000;
|
|
429
|
+
|
|
430
|
+
if (elapsed > 0) { // we don't want to trigger things if audio is still being loaded.
|
|
431
|
+
this._captions.update(tick.delta / 1000);
|
|
432
|
+
|
|
433
|
+
if (audioEventQueue) {
|
|
434
|
+
audioEventQueue.getEvents(elapsed).forEach((entry) => this.trigger(entry));
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Plays the current audio item and begins preloading the next item.
|
|
441
|
+
* @private
|
|
442
|
+
*/
|
|
443
|
+
async _playSound () {
|
|
444
|
+
const
|
|
445
|
+
play = () => {
|
|
446
|
+
const
|
|
447
|
+
sound = platypus.assetCache.get(soundId);
|
|
448
|
+
|
|
449
|
+
this._soundInstance = sound.play({
|
|
450
|
+
complete: this._onSoundFinished,
|
|
451
|
+
volume: this.volume * this.masterVolume
|
|
452
|
+
});
|
|
453
|
+
this.audioEventQueue = null;
|
|
454
|
+
if (this.tagReader) {
|
|
455
|
+
this.tagReader.read(sound.media?.file ?? sound.url, {
|
|
456
|
+
onSuccess: ({tags}) => {
|
|
457
|
+
const
|
|
458
|
+
{SYLT} = tags;
|
|
459
|
+
|
|
460
|
+
if (SYLT) {
|
|
461
|
+
const
|
|
462
|
+
list = Array.isArray(SYLT) ? SYLT : [SYLT],
|
|
463
|
+
combinedEventList = list.reduce((events, {data}) => {
|
|
464
|
+
events.push(...data.synchronisedText.map(({text: content, timeStamp: time}) => ({
|
|
465
|
+
event: 'lyric',
|
|
466
|
+
time,
|
|
467
|
+
message: {
|
|
468
|
+
language: data.language,
|
|
469
|
+
descriptor: data.descriptor,
|
|
470
|
+
contentType: data.contentType,
|
|
471
|
+
content
|
|
472
|
+
},
|
|
473
|
+
interruptable: true
|
|
474
|
+
})));
|
|
475
|
+
return events;
|
|
476
|
+
}, []);
|
|
477
|
+
|
|
478
|
+
this.audioEventQueue = this.audioEventQueue || TimeEventList.setUp();
|
|
479
|
+
this.audioEventQueue.addEvents(combinedEventList);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
onError: (error) => platypus.debug.warn(error)
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
if (this._captions) {
|
|
486
|
+
this._captions.start(sound, soundId, 0);
|
|
487
|
+
this.game.on("tick", this._syncCaptionToSound);
|
|
488
|
+
}
|
|
489
|
+
if (this.playQueue.length && this.interruptable) { // We need to skip on ahead, because new VO was played while this or a prior one was loading.
|
|
490
|
+
this.checkQueue();
|
|
491
|
+
} else {
|
|
492
|
+
for (let i = this._listCounter + 1; i < this.voList.length; ++i) {
|
|
493
|
+
const next = this.voList[i];
|
|
494
|
+
if (typeof next === "string") {
|
|
495
|
+
const
|
|
496
|
+
arr = arrayCache.setUp({
|
|
497
|
+
id: this._currentVO,
|
|
498
|
+
src: this._currentVO
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
this.assetCache.load(arr);
|
|
502
|
+
|
|
503
|
+
arrayCache.recycle(arr);
|
|
504
|
+
|
|
505
|
+
if (this.preloadingNextSound) {
|
|
506
|
+
this.unloadSound(this.preloadingNextSound);
|
|
507
|
+
}
|
|
508
|
+
this.preloadingNextSound = next;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
currentVO = this._currentVO,
|
|
515
|
+
soundId = platypus.assetCache.getFileId(currentVO),
|
|
516
|
+
arr = arrayCache.setUp({
|
|
517
|
+
alias: [soundId],
|
|
518
|
+
src: `${currentVO}`
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
this.currentlyLoadingAudio = true;
|
|
522
|
+
|
|
523
|
+
try {
|
|
524
|
+
await this.assetCache.load(arr, null, () => {
|
|
525
|
+
this.currentlyLoadingAudio = false;
|
|
526
|
+
if (this.stoppedWhileLoading) {
|
|
527
|
+
this.stoppedWhileLoading = false;
|
|
528
|
+
if (this.playQueue.length) { // Already more queued up, so we'll roll the stop into it here
|
|
529
|
+
play();
|
|
530
|
+
} else {
|
|
531
|
+
play();
|
|
532
|
+
this.stop();
|
|
533
|
+
}
|
|
534
|
+
} else if (currentVO === this._currentVO) {
|
|
535
|
+
play();
|
|
536
|
+
} else {
|
|
537
|
+
platypus.debug.warn('VOPlayer: Asset loading out of order.');
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
} catch (e) {
|
|
541
|
+
platypus.debug.warn('Unable to load voice-over.', e);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
arrayCache.recycle(arr);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
checkQueue () {
|
|
548
|
+
if (this.playQueue.length) {
|
|
549
|
+
const
|
|
550
|
+
vo = greenSplice(this.playQueue, 0);
|
|
551
|
+
|
|
552
|
+
this.play(vo.idOrList, vo.callback, vo.cancelledCallback, vo.interruptable);
|
|
553
|
+
|
|
554
|
+
vo.recycle();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
clearQueue () {
|
|
559
|
+
while (this.playQueue.length) {
|
|
560
|
+
greenSplice(this.playQueue, 0).recycle();
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Stops playback of any audio/timer.
|
|
566
|
+
* @method platypus.VOPlayer#stop
|
|
567
|
+
* @public
|
|
568
|
+
*/
|
|
569
|
+
stop (completeStop = false) {
|
|
570
|
+
if (!this.startingNewTrack && (this.interruptable || completeStop)) {
|
|
571
|
+
this.clearQueue();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (this.currentlyLoadingAudio) {
|
|
575
|
+
this.stoppedWhileLoading = true;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const c = this._cancelledCallback;
|
|
579
|
+
|
|
580
|
+
this.paused = false;
|
|
581
|
+
if (this._soundInstance) {
|
|
582
|
+
this._soundInstance.stop();
|
|
583
|
+
this.unloadSound(this._currentVO);
|
|
584
|
+
this._soundInstance = null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (this._currentVO) {
|
|
588
|
+
if (typeof this._currentVO === "string") {
|
|
589
|
+
this.trigger("stop", this._currentVO);
|
|
590
|
+
} else if (typeof this._currentVO !== "function") {
|
|
591
|
+
this.trigger("stop", null);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
this._currentVO = null;
|
|
596
|
+
if (this._captions && this._captions.activeCaption) {
|
|
597
|
+
this._captions.stop();
|
|
598
|
+
}
|
|
599
|
+
this.game.off("tick", this._syncCaptionToSound);
|
|
600
|
+
this.game.off("tick", this._updateSilence);
|
|
601
|
+
if (this.voList) {
|
|
602
|
+
for (let i = this._listCounter + 1; i < this.voList.length; i++) {
|
|
603
|
+
if (typeof this.voList[i] === 'function') {
|
|
604
|
+
this.voList[i](); // Make sure all events are triggered.
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
this.voList = null;
|
|
608
|
+
}
|
|
609
|
+
this._timer = 0;
|
|
610
|
+
this._callback = null;
|
|
611
|
+
|
|
612
|
+
this._cancelledCallback = null;
|
|
613
|
+
if (c) {
|
|
614
|
+
c();
|
|
615
|
+
}
|
|
616
|
+
if (!this.startingNewTrack && !this.interruptable && !completeStop) {
|
|
617
|
+
this.checkQueue();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Sets the volume of VO playback.
|
|
623
|
+
*
|
|
624
|
+
* @method platypus.VOPlayer#setVolume
|
|
625
|
+
* @param {Number} volume
|
|
626
|
+
*/
|
|
627
|
+
setVolume (volume) {
|
|
628
|
+
this.volume = volume;
|
|
629
|
+
if (this._soundInstance) {
|
|
630
|
+
this._soundInstance.volume = this.volume * this.masterVolume;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Sets the master volume of VO playback.
|
|
636
|
+
*
|
|
637
|
+
* @method platypus.VOPlayer#setMasterVolume
|
|
638
|
+
* @param {Number} volume
|
|
639
|
+
*/
|
|
640
|
+
setMasterVolume (volume) {
|
|
641
|
+
this.masterVolume = volume;
|
|
642
|
+
this.setVolume(this.volume);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Whether to mute captions.
|
|
647
|
+
*
|
|
648
|
+
* @method platypus.VOPlayer#setCaptionMute
|
|
649
|
+
* @param {Boolean} muted
|
|
650
|
+
*/
|
|
651
|
+
setCaptionMute (muted) {
|
|
652
|
+
this.captionMute = muted;
|
|
653
|
+
if (this._captions) {
|
|
654
|
+
this._captions.renderer.renderTarget.style.display = muted ? 'none' : 'block';
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Unloads an audio track this VOPlayer has played.
|
|
660
|
+
* @method platypus.VOPlayer#unloadSound
|
|
661
|
+
* @param sound {string} Sound to unload.
|
|
662
|
+
* @public
|
|
663
|
+
*/
|
|
664
|
+
unloadSound (sound) {
|
|
665
|
+
this.assetCache.delete(sound);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Cleans up this VOPlayer.
|
|
670
|
+
* @method platypus.VOPlayer#destroy
|
|
671
|
+
* @public
|
|
672
|
+
*/
|
|
673
|
+
destroy () {
|
|
674
|
+
this.stop();
|
|
675
|
+
this.voList = null;
|
|
676
|
+
this._currentVO = null;
|
|
677
|
+
this._soundInstance = null;
|
|
678
|
+
this._callback = null;
|
|
679
|
+
this._cancelledCallback = null;
|
|
680
|
+
this._captions = null;
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
export default VOPlayer;
|