@rbxts/sound-manager 2.0.1 → 2.1.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/README.md
CHANGED
|
@@ -25,5 +25,43 @@ Sounds.preloadAll();
|
|
|
25
25
|
Sounds.play("SCP096");
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
## Roadmap
|
|
29
|
+
|
|
30
|
+
### Core API
|
|
31
|
+
- [x] load
|
|
32
|
+
- [x] play
|
|
33
|
+
- [x] stop / stopAll
|
|
34
|
+
- [x] preload / preloadAll
|
|
35
|
+
- [x] fadeIn / fadeOut
|
|
36
|
+
- [x] reset / resetAll
|
|
37
|
+
- [x] setTimePosition
|
|
38
|
+
- [x] setVolume / setGlobalVolume
|
|
39
|
+
- [x] onEnd
|
|
40
|
+
- [x] isPlaying
|
|
41
|
+
|
|
42
|
+
### Development Utilities
|
|
43
|
+
- [ ] Total Sound Count function
|
|
44
|
+
- [ ] Currently Playing Sounds function
|
|
45
|
+
- [ ] Sound Instance Getter function
|
|
46
|
+
- [ ] Sound Properties Getter function
|
|
47
|
+
|
|
48
|
+
### Category API
|
|
49
|
+
- [x] playCategory
|
|
50
|
+
- [x] stopCategory / stopAllCategories
|
|
51
|
+
- [x] setCategoryVolume / setGlobalCategoryVolume
|
|
52
|
+
- [x] fadeInCategory / fadeOutCategory
|
|
53
|
+
- [x] preloadCategory / preloadAllCategories
|
|
54
|
+
- [x] isCategoryPlaying
|
|
55
|
+
- [x] onCategoryEnd
|
|
56
|
+
- [x] resetCategory / resetAllCategories
|
|
57
|
+
- [x] playSoundFromCategory
|
|
58
|
+
|
|
59
|
+
### Features
|
|
60
|
+
- [x] Sound Autocompletion
|
|
61
|
+
- [ ] Sound Categories
|
|
62
|
+
- [ ] Sound Creation functions
|
|
63
|
+
- [ ] Sound Priority
|
|
64
|
+
|
|
65
|
+
|
|
28
66
|
For more Details:
|
|
29
67
|
https://dev-lukas0.github.io/Sound-Manager-Docs/
|
|
@@ -6,4 +6,17 @@ import { CategoryOptions } from "./options";
|
|
|
6
6
|
export declare function createSoundCategoryRegistry<T extends Record<string, CategoryOptions>>(definitions: T): {
|
|
7
7
|
loadCategory: (name: keyof T) => void;
|
|
8
8
|
playCategory: <C extends keyof T>(name: C) => void;
|
|
9
|
+
stopCategory: <C extends keyof T>(name: C) => void;
|
|
10
|
+
stopAllCategories: () => void;
|
|
11
|
+
setCategoryVolume: <C extends keyof T>(category: C, volume: number) => void;
|
|
12
|
+
setGlobalCategoryVolume: (volume: number) => void;
|
|
13
|
+
isCategoryPlaying: <C extends keyof T>(category: C) => boolean;
|
|
14
|
+
playSoundFromCategory: <C extends keyof T, S extends keyof T[C]["sounds"]>(category: C, sound: S) => void;
|
|
15
|
+
resetCategory: <C extends keyof T>(category: C) => void;
|
|
16
|
+
resetAllCategories: <C extends keyof T>(category: C) => void;
|
|
17
|
+
preloadAllCategories: () => void;
|
|
18
|
+
preloadCategory: <C extends keyof T>(category: C) => void;
|
|
19
|
+
fadeInCategory: <C extends keyof T>(category: C, duration: number, volume: number) => void;
|
|
20
|
+
fadeOutCategory: <C extends keyof T>(category: C, duration: number, targetVolume?: number) => void;
|
|
21
|
+
onCategoryEnd: <C extends keyof T>(category: C, callback: () => void) => void;
|
|
9
22
|
};
|
|
@@ -79,9 +79,392 @@ local function createSoundCategoryRegistry(definitions)
|
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
end
|
|
82
|
+
--[[
|
|
83
|
+
*
|
|
84
|
+
* Stop every Sound from a Sound Category
|
|
85
|
+
* @param name Sound Category
|
|
86
|
+
|
|
87
|
+
]]
|
|
88
|
+
local function stopCategory(name)
|
|
89
|
+
local config = definitions[name]
|
|
90
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
91
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
92
|
+
if not soundsFolder then
|
|
93
|
+
return nil
|
|
94
|
+
end
|
|
95
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
96
|
+
if not categoryFolder then
|
|
97
|
+
return nil
|
|
98
|
+
end
|
|
99
|
+
for sound in pairs(config.sounds) do
|
|
100
|
+
local _sound = categoryFolder:FindFirstChild(sound)
|
|
101
|
+
if not _sound then
|
|
102
|
+
continue
|
|
103
|
+
end
|
|
104
|
+
local _result = _sound
|
|
105
|
+
if _result ~= nil then
|
|
106
|
+
_result = _result:IsA("Sound")
|
|
107
|
+
end
|
|
108
|
+
if _result then
|
|
109
|
+
_sound:Stop()
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
--[[
|
|
114
|
+
*
|
|
115
|
+
* Stops every Sound from every Sound Category
|
|
116
|
+
|
|
117
|
+
]]
|
|
118
|
+
local function stopAllCategories()
|
|
119
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
120
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
121
|
+
if not soundsFolder then
|
|
122
|
+
return nil
|
|
123
|
+
end
|
|
124
|
+
for _, category in soundsFolder:GetChildren() do
|
|
125
|
+
if not category:IsA("Folder") then
|
|
126
|
+
continue
|
|
127
|
+
end
|
|
128
|
+
for _1, sound in category:GetChildren() do
|
|
129
|
+
if not sound:IsA("Sound") then
|
|
130
|
+
continue
|
|
131
|
+
end
|
|
132
|
+
sound:Stop()
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
--[[
|
|
137
|
+
*
|
|
138
|
+
* Set the Volume of a Sound Category
|
|
139
|
+
* @param category Sound Category
|
|
140
|
+
* @param volume Volume
|
|
141
|
+
|
|
142
|
+
]]
|
|
143
|
+
local function setCategoryVolume(category, volume)
|
|
144
|
+
local config = definitions[category]
|
|
145
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
146
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
147
|
+
if not soundsFolder then
|
|
148
|
+
return nil
|
|
149
|
+
end
|
|
150
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
151
|
+
if not categoryFolder then
|
|
152
|
+
return nil
|
|
153
|
+
end
|
|
154
|
+
for _, sound in categoryFolder:GetChildren() do
|
|
155
|
+
if not sound:IsA("Sound") then
|
|
156
|
+
continue
|
|
157
|
+
end
|
|
158
|
+
sound.Volume = volume
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
--[[
|
|
162
|
+
*
|
|
163
|
+
* Set the Global Volume of every Sound Category
|
|
164
|
+
* @param volume Volume
|
|
165
|
+
|
|
166
|
+
]]
|
|
167
|
+
local function setGlobalCategoryVolume(volume)
|
|
168
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
169
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
170
|
+
if not soundsFolder then
|
|
171
|
+
return nil
|
|
172
|
+
end
|
|
173
|
+
for _, category in soundsFolder:GetChildren() do
|
|
174
|
+
if not category:IsA("Folder") then
|
|
175
|
+
continue
|
|
176
|
+
end
|
|
177
|
+
for _1, sound in category:GetChildren() do
|
|
178
|
+
if not sound:IsA("Sound") then
|
|
179
|
+
continue
|
|
180
|
+
end
|
|
181
|
+
sound.Volume = volume
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
--[[
|
|
186
|
+
*
|
|
187
|
+
* Whether a Category is playing or not
|
|
188
|
+
* @param category Sound Category
|
|
189
|
+
* @returns Whether a Category is playing or not
|
|
190
|
+
|
|
191
|
+
]]
|
|
192
|
+
local function isCategoryPlaying(category)
|
|
193
|
+
local config = definitions[category]
|
|
194
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
195
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
196
|
+
if not soundsFolder then
|
|
197
|
+
return false
|
|
198
|
+
end
|
|
199
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
200
|
+
if not categoryFolder then
|
|
201
|
+
return false
|
|
202
|
+
end
|
|
203
|
+
local _exp = categoryFolder:GetChildren()
|
|
204
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
205
|
+
local _newValue = {}
|
|
206
|
+
local _callback = function(sound)
|
|
207
|
+
return sound:IsA("Sound")
|
|
208
|
+
end
|
|
209
|
+
local _length = 0
|
|
210
|
+
for _k, _v in _exp do
|
|
211
|
+
if _callback(_v, _k - 1, _exp) == true then
|
|
212
|
+
_length += 1
|
|
213
|
+
_newValue[_length] = _v
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
217
|
+
local sounds = _newValue
|
|
218
|
+
if #sounds == 0 then
|
|
219
|
+
return false
|
|
220
|
+
end
|
|
221
|
+
for _, sound in sounds do
|
|
222
|
+
if not sound.IsPlaying then
|
|
223
|
+
return false
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
return true
|
|
227
|
+
end
|
|
228
|
+
--[[
|
|
229
|
+
*
|
|
230
|
+
* Play a Sound from a Category
|
|
231
|
+
* @param category Sound Category
|
|
232
|
+
* @param sound Sound Instance
|
|
233
|
+
|
|
234
|
+
]]
|
|
235
|
+
local function playSoundFromCategory(category, sound)
|
|
236
|
+
loadCategory(category)
|
|
237
|
+
local config = definitions[category]
|
|
238
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
239
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
240
|
+
if not soundsFolder then
|
|
241
|
+
return nil
|
|
242
|
+
end
|
|
243
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
244
|
+
if not categoryFolder then
|
|
245
|
+
return nil
|
|
246
|
+
end
|
|
247
|
+
local soundInstance = categoryFolder:FindFirstChild(sound)
|
|
248
|
+
local _result = soundInstance
|
|
249
|
+
if _result ~= nil then
|
|
250
|
+
_result = _result:IsA("Sound")
|
|
251
|
+
end
|
|
252
|
+
if _result then
|
|
253
|
+
soundInstance:Play()
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
--[[
|
|
257
|
+
*
|
|
258
|
+
* Reset a Sound Category Timeposition to 0
|
|
259
|
+
* @param category Sound Category
|
|
260
|
+
|
|
261
|
+
]]
|
|
262
|
+
local function resetCategory(category)
|
|
263
|
+
local config = definitions[category]
|
|
264
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
265
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
266
|
+
if not soundsFolder then
|
|
267
|
+
return nil
|
|
268
|
+
end
|
|
269
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
270
|
+
if not categoryFolder then
|
|
271
|
+
return nil
|
|
272
|
+
end
|
|
273
|
+
for _, sound in categoryFolder:GetChildren() do
|
|
274
|
+
if not sound:IsA("Sound") then
|
|
275
|
+
continue
|
|
276
|
+
end
|
|
277
|
+
sound.TimePosition = 0
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
--[[
|
|
281
|
+
*
|
|
282
|
+
* Resets every Sound from every Sound Category
|
|
283
|
+
* @param category Sound Category
|
|
284
|
+
|
|
285
|
+
]]
|
|
286
|
+
local function resetAllCategories(category)
|
|
287
|
+
local config = definitions[category]
|
|
288
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
289
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
290
|
+
if not soundsFolder then
|
|
291
|
+
return nil
|
|
292
|
+
end
|
|
293
|
+
for _, folder in soundsFolder:GetChildren() do
|
|
294
|
+
if not folder:IsA("Folder") then
|
|
295
|
+
continue
|
|
296
|
+
end
|
|
297
|
+
for _1, sound in folder:GetChildren() do
|
|
298
|
+
if not sound:IsA("Sound") then
|
|
299
|
+
continue
|
|
300
|
+
end
|
|
301
|
+
sound.TimePosition = 0
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
--[[
|
|
306
|
+
*
|
|
307
|
+
* Preloads a Sound Category
|
|
308
|
+
* @param category Sound Category
|
|
309
|
+
|
|
310
|
+
]]
|
|
311
|
+
local function preloadCategory(category)
|
|
312
|
+
loadCategory(category)
|
|
313
|
+
end
|
|
314
|
+
--[[
|
|
315
|
+
*
|
|
316
|
+
* Preloads every Sound Category
|
|
317
|
+
|
|
318
|
+
]]
|
|
319
|
+
local function preloadAllCategories()
|
|
320
|
+
for category in pairs(definitions) do
|
|
321
|
+
loadCategory(category)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
--[[
|
|
325
|
+
*
|
|
326
|
+
* Smoothly fade in a Sound Category
|
|
327
|
+
* @param category Sound Category
|
|
328
|
+
* @param duration Duration
|
|
329
|
+
* @param volume Volume
|
|
330
|
+
|
|
331
|
+
]]
|
|
332
|
+
local function fadeInCategory(category, duration, volume)
|
|
333
|
+
local config = definitions[category]
|
|
334
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
335
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
336
|
+
if not soundsFolder then
|
|
337
|
+
return nil
|
|
338
|
+
end
|
|
339
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
340
|
+
if not categoryFolder then
|
|
341
|
+
return nil
|
|
342
|
+
end
|
|
343
|
+
for _, sound in categoryFolder:GetChildren() do
|
|
344
|
+
if not sound:IsA("Sound") then
|
|
345
|
+
continue
|
|
346
|
+
end
|
|
347
|
+
sound.Volume = 0
|
|
348
|
+
sound:Play()
|
|
349
|
+
local step = 0.05
|
|
350
|
+
local interval = duration * step
|
|
351
|
+
task.spawn(function()
|
|
352
|
+
local vol = 0
|
|
353
|
+
while vol < volume do
|
|
354
|
+
vol += step
|
|
355
|
+
sound.Volume = math.clamp(vol, 0, volume)
|
|
356
|
+
task.wait(interval)
|
|
357
|
+
end
|
|
358
|
+
end)
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
--[[
|
|
362
|
+
*
|
|
363
|
+
* Smoothly fade out a Sound Category
|
|
364
|
+
* @param category Sound Category
|
|
365
|
+
* @param duration Duration
|
|
366
|
+
* @param targetVolume Target Volume
|
|
367
|
+
|
|
368
|
+
]]
|
|
369
|
+
local function fadeOutCategory(category, duration, targetVolume)
|
|
370
|
+
local config = definitions[category]
|
|
371
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
372
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
373
|
+
if not soundsFolder then
|
|
374
|
+
return nil
|
|
375
|
+
end
|
|
376
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
377
|
+
if not categoryFolder then
|
|
378
|
+
return nil
|
|
379
|
+
end
|
|
380
|
+
for _, sound in categoryFolder:GetChildren() do
|
|
381
|
+
if not sound:IsA("Sound") then
|
|
382
|
+
continue
|
|
383
|
+
end
|
|
384
|
+
local startVolume = sound.Volume
|
|
385
|
+
local _condition = targetVolume
|
|
386
|
+
if _condition == nil then
|
|
387
|
+
_condition = 0
|
|
388
|
+
end
|
|
389
|
+
local endVolume = _condition
|
|
390
|
+
local step = 0.05
|
|
391
|
+
local interval = duration * step
|
|
392
|
+
task.spawn(function()
|
|
393
|
+
local vol = startVolume
|
|
394
|
+
while vol > endVolume do
|
|
395
|
+
vol = math.clamp(vol - step, endVolume, startVolume)
|
|
396
|
+
sound.Volume = vol
|
|
397
|
+
task.wait(interval)
|
|
398
|
+
end
|
|
399
|
+
sound.Volume = endVolume
|
|
400
|
+
if endVolume == 0 then
|
|
401
|
+
sound:Stop()
|
|
402
|
+
end
|
|
403
|
+
end)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
--[[
|
|
407
|
+
*
|
|
408
|
+
* Does something on Category end
|
|
409
|
+
* @param category Sound Category
|
|
410
|
+
* @param callback Callback
|
|
411
|
+
|
|
412
|
+
]]
|
|
413
|
+
local function onCategoryEnd(category, callback)
|
|
414
|
+
local config = definitions[category]
|
|
415
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
416
|
+
local soundsFolder = ReplicatedStorage:FindFirstChild("Sounds")
|
|
417
|
+
if not soundsFolder then
|
|
418
|
+
return nil
|
|
419
|
+
end
|
|
420
|
+
local categoryFolder = soundsFolder:FindFirstChild(config.category)
|
|
421
|
+
if not categoryFolder then
|
|
422
|
+
return nil
|
|
423
|
+
end
|
|
424
|
+
local _exp = categoryFolder:GetChildren()
|
|
425
|
+
-- ▼ ReadonlyArray.filter ▼
|
|
426
|
+
local _newValue = {}
|
|
427
|
+
local _callback = function(s)
|
|
428
|
+
return s:IsA("Sound")
|
|
429
|
+
end
|
|
430
|
+
local _length = 0
|
|
431
|
+
for _k, _v in _exp do
|
|
432
|
+
if _callback(_v, _k - 1, _exp) == true then
|
|
433
|
+
_length += 1
|
|
434
|
+
_newValue[_length] = _v
|
|
435
|
+
end
|
|
436
|
+
end
|
|
437
|
+
-- ▲ ReadonlyArray.filter ▲
|
|
438
|
+
local sounds = _newValue
|
|
439
|
+
if #sounds == 0 then
|
|
440
|
+
return nil
|
|
441
|
+
end
|
|
442
|
+
local remaining = #sounds
|
|
443
|
+
for _, sound in sounds do
|
|
444
|
+
sound.Ended:Connect(function()
|
|
445
|
+
remaining -= 1
|
|
446
|
+
if remaining <= 0 then
|
|
447
|
+
callback()
|
|
448
|
+
end
|
|
449
|
+
end)
|
|
450
|
+
end
|
|
451
|
+
end
|
|
82
452
|
return {
|
|
83
453
|
loadCategory = loadCategory,
|
|
84
454
|
playCategory = playCategory,
|
|
455
|
+
stopCategory = stopCategory,
|
|
456
|
+
stopAllCategories = stopAllCategories,
|
|
457
|
+
setCategoryVolume = setCategoryVolume,
|
|
458
|
+
setGlobalCategoryVolume = setGlobalCategoryVolume,
|
|
459
|
+
isCategoryPlaying = isCategoryPlaying,
|
|
460
|
+
playSoundFromCategory = playSoundFromCategory,
|
|
461
|
+
resetCategory = resetCategory,
|
|
462
|
+
resetAllCategories = resetAllCategories,
|
|
463
|
+
preloadAllCategories = preloadAllCategories,
|
|
464
|
+
preloadCategory = preloadCategory,
|
|
465
|
+
fadeInCategory = fadeInCategory,
|
|
466
|
+
fadeOutCategory = fadeOutCategory,
|
|
467
|
+
onCategoryEnd = onCategoryEnd,
|
|
85
468
|
}
|
|
86
469
|
end
|
|
87
470
|
return {
|
|
@@ -18,4 +18,5 @@ export declare function createSoundRegistry<T extends Record<string, SoundOption
|
|
|
18
18
|
setVolume: (sound: keyof T, volume: number) => void;
|
|
19
19
|
resetAll: (sound: keyof T) => void;
|
|
20
20
|
onEnd: (sound: keyof T, callback: () => void) => void;
|
|
21
|
+
isPlaying: (sound: keyof T) => boolean;
|
|
21
22
|
};
|
|
@@ -222,7 +222,7 @@ local function createSoundRegistry(definitions)
|
|
|
222
222
|
end
|
|
223
223
|
--[[
|
|
224
224
|
*
|
|
225
|
-
*
|
|
225
|
+
* Does something on Sound end
|
|
226
226
|
* @param name Sound Name
|
|
227
227
|
* @param callback Callback
|
|
228
228
|
|
|
@@ -243,6 +243,21 @@ local function createSoundRegistry(definitions)
|
|
|
243
243
|
local function preload(sound)
|
|
244
244
|
load(sound)
|
|
245
245
|
end
|
|
246
|
+
--[[
|
|
247
|
+
*
|
|
248
|
+
* Check whether a Sound is playing
|
|
249
|
+
* @param sound Sound Instance
|
|
250
|
+
* @returns Boolean
|
|
251
|
+
|
|
252
|
+
]]
|
|
253
|
+
local function isPlaying(sound)
|
|
254
|
+
local _sound = folder:WaitForChild(sound)
|
|
255
|
+
if _sound.IsPlaying == true then
|
|
256
|
+
return true
|
|
257
|
+
else
|
|
258
|
+
return false
|
|
259
|
+
end
|
|
260
|
+
end
|
|
246
261
|
return {
|
|
247
262
|
play = play,
|
|
248
263
|
stop = stop,
|
|
@@ -258,6 +273,7 @@ local function createSoundRegistry(definitions)
|
|
|
258
273
|
setVolume = setVolume,
|
|
259
274
|
resetAll = resetAll,
|
|
260
275
|
onEnd = onEnd,
|
|
276
|
+
isPlaying = isPlaying,
|
|
261
277
|
}
|
|
262
278
|
end
|
|
263
279
|
return {
|