q5 2.14.4 → 2.14.5

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/src/q5-record.js DELETED
@@ -1,366 +0,0 @@
1
- Q5.modules.record = ($, q) => {
2
- class Q5Recorder {
3
- constructor(canvas, options = {}) {
4
- this.canvas = canvas || document.querySelector('canvas');
5
- if (!this.canvas) {
6
- throw new Error('Canvas not found');
7
- }
8
-
9
- this.mediaRecorder = null;
10
- this.chunks = [];
11
- this.isRecording = false;
12
- this.isPaused = false;
13
- this.stream = this.canvas.captureStream($.getTargetFrameRate());
14
- this.options = {
15
- x: options.x,
16
- y: options.y,
17
- display: options.x !== undefined || options.y !== undefined ? 'flex' : 'none',
18
- ...options
19
- };
20
- this.startTime = null;
21
- this.timerInterval = null;
22
- this.elapsedTime = 0;
23
- this.createUI();
24
- }
25
-
26
- createUI() {
27
- document.head.insertAdjacentHTML(
28
- 'afterbegin',
29
- `<style>
30
- .recorder-wrapper {
31
- position: absolute;
32
- top: ${this.options.y}px;
33
- left: ${this.options.x}px;
34
- z-index: 1000;
35
- gap: 7px;
36
- background: rgb(26, 27, 29);
37
- padding: 7px;
38
- border-radius: 30px;
39
- box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
40
- border: 2px solid transparent;
41
- opacity: 0.5;
42
- transition: all 0.3s;
43
- display: ${this.options.display};
44
- overflow: hidden;
45
- }
46
-
47
- .recorder-wrapper:hover {
48
- opacity: 0.95;
49
- }
50
-
51
- .recorder-wrapper.recording {
52
- border-color: #cc3e44;
53
- }
54
-
55
- .recorder-wrapper.recording .start-button {
56
- color: #cc3e44;
57
- opacity: 1;
58
- }
59
-
60
- .recorder-wrapper.recording .format-selector,
61
- .recorder-wrapper.recording .download-button {
62
- width: 0px;
63
- height: 0px;
64
- opacity: 0;
65
- min-width: 0px;
66
- }
67
-
68
- .recorder-wrapper button,
69
- .recorder-wrapper select,
70
- .recorder-wrapper .recorder-timer {
71
- cursor: pointer;
72
- font-size: 13px;
73
- padding: 5px 9px;
74
- border-radius: 30px;
75
- border: none;
76
- outline: none;
77
- background-color: rgb(35, 37, 41);
78
- color: rgb(212, 218, 230);
79
- font-family: 'Arial';
80
- box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;
81
- border: thin solid rgb(70, 73, 78);
82
- line-height: 24px;
83
- min-width: 37px;
84
- max-width: 37px;
85
- transition: width 0.3s, height 0.3s, opacity 0.2s;
86
- overflow: hidden;
87
- }
88
-
89
- .recorder-wrapper .recorder-timer {
90
- min-width: 69px;
91
- max-width:100px;
92
- }
93
-
94
- .recorder-wrapper select {
95
- max-width:100px;
96
- }
97
-
98
- .recorder-wrapper .format-selector {
99
- min-width: 100px;
100
- }
101
-
102
- .recorder-wrapper select:hover,
103
- .recorder-wrapper button:hover {
104
- background-color: rgb(41, 43, 48);
105
- }
106
-
107
- .recorder-wrapper button:disabled {
108
- opacity: 0.5;
109
- color: rgb(150, 155, 165);
110
- cursor: not-allowed;
111
- }
112
-
113
- .recorder-wrapper .download-button {
114
- font-size: 18px;
115
- }
116
- </style>`
117
- );
118
-
119
- let supportedFormats = [
120
- { label: 'H.264', mimeType: 'video/mp4; codecs="avc1.42E01E"' },
121
- { label: 'VP9', mimeType: 'video/mp4; codecs=vp9' }
122
- ].filter((format) => MediaRecorder.isTypeSupported(format.mimeType));
123
-
124
- let wrapper = document.createElement('div');
125
- wrapper.className = 'recorder-wrapper';
126
- wrapper.innerHTML = `
127
- <button></button><button></button><span class="recorder-timer"></span>`;
128
-
129
- let formatSelector = document.createElement('select');
130
- for (let format of supportedFormats) {
131
- let option = document.createElement('option');
132
- option.value = format.mimeType;
133
- option.textContent = format.label;
134
- formatSelector.appendChild(option);
135
- }
136
- wrapper.appendChild(formatSelector);
137
-
138
- document.body.appendChild(wrapper);
139
-
140
- this.wrapper = wrapper;
141
- let recordPauseButton = (this.recordPauseButton = wrapper.children[0]);
142
- let deleteSaveButton = (this.deleteSaveButton = wrapper.children[1]);
143
- this.timerDisplay = wrapper.children[2];
144
- this.formatSelector = formatSelector;
145
-
146
- this.encoderSettings = {
147
- mimeType: formatSelector.value,
148
- videoBitsPerSecond: 10000000
149
- };
150
-
151
- formatSelector.addEventListener('change', () => {
152
- this.encoderSettings.mimeType = formatSelector.value;
153
- });
154
-
155
- recordPauseButton.addEventListener('click', () => {
156
- if (!this.isRecording) this.start();
157
- else if (!this.isPaused) this.pauseRecording();
158
- else this.resumeRecording();
159
- });
160
-
161
- deleteSaveButton.addEventListener('click', () => {
162
- if (this.isPaused) this.saveRecording();
163
- else {
164
- this.stop();
165
- this.chunks = [];
166
- this.resetUI();
167
- }
168
- });
169
-
170
- this.resetUI();
171
- }
172
-
173
- start(videoSettings = {}) {
174
- if (this.isRecording) return;
175
-
176
- if (videoSettings.mimeType) this.encoderSettings.mimeType = videoSettings.mimeType;
177
- if (videoSettings.videoBitsPerSecond) this.encoderSettings.videoBitsPerSecond = videoSettings.videoBitsPerSecond;
178
-
179
- try {
180
- this.mediaRecorder = new MediaRecorder(this.stream, this.encoderSettings);
181
- } catch (e) {
182
- console.error('Failed to initialize MediaRecorder:', e);
183
- return;
184
- }
185
-
186
- this.chunks = [];
187
- this.mediaRecorder.ondataavailable = (event) => {
188
- if (event.data.size > 0) {
189
- this.chunks.push(event.data);
190
- }
191
- };
192
-
193
- this.mediaRecorder.start();
194
- this.isRecording = true;
195
- this.isPaused = false;
196
-
197
- this.recordPauseButton.innerHTML = '⏸';
198
- this.recordPauseButton.title = 'Pause Recording';
199
- this.deleteSaveButton.innerHTML = '🗑️';
200
- this.deleteSaveButton.title = 'Delete Recording';
201
-
202
- this.deleteSaveButton.disabled = false;
203
- this.wrapper.classList.add('recording');
204
-
205
- this.startTime = Date.now();
206
- this.timerInterval = setInterval(() => {
207
- this.updateTimer();
208
- }, 50);
209
- }
210
-
211
- pauseRecording() {
212
- if (!this.isRecording || this.isPaused) return;
213
-
214
- this.mediaRecorder.pause();
215
- this.isPaused = true;
216
-
217
- this.recordPauseButton.innerHTML = '⏺';
218
- this.recordPauseButton.title = 'Resume Recording';
219
- this.deleteSaveButton.innerHTML = '💾';
220
- this.deleteSaveButton.title = 'Save Recording';
221
-
222
- this.elapsedTime += Date.now() - this.startTime;
223
- clearInterval(this.timerInterval);
224
- }
225
-
226
- resumeRecording() {
227
- if (!this.isRecording || !this.isPaused) return;
228
-
229
- this.mediaRecorder.resume();
230
- this.isPaused = false;
231
-
232
- this.recordPauseButton.innerHTML = '⏸';
233
- this.recordPauseButton.title = 'Pause Recording';
234
- this.deleteSaveButton.innerHTML = '🗑️';
235
- this.deleteSaveButton.title = 'Delete Recording';
236
-
237
- this.startTime = Date.now();
238
- this.timerInterval = setInterval(() => {
239
- this.updateTimer();
240
- }, 50);
241
- }
242
-
243
- stop() {
244
- if (!this.isRecording) return;
245
-
246
- this.mediaRecorder.stop();
247
- this.isRecording = false;
248
- this.isPaused = false;
249
-
250
- this.wrapper.classList.remove('recording');
251
-
252
- this.elapsedTime += Date.now() - this.startTime;
253
- clearInterval(this.timerInterval);
254
- this.updateTimer();
255
- }
256
-
257
- saveRecording(fileName = 'recording') {
258
- if (this.isRecording) {
259
- this.stop();
260
- this.mediaRecorder.onstop = () => {
261
- this.exportRecording(fileName);
262
- };
263
- } else {
264
- this.exportRecording(fileName);
265
- }
266
- }
267
-
268
- exportRecording(fileName) {
269
- if (this.chunks.length === 0) return;
270
-
271
- let type = this.encoderSettings.mimeType;
272
- let extension = type.slice(5, type.indexOf(';') - 5);
273
- let fullFileName = `${fileName}.${extension}`;
274
- let blob = new Blob(this.chunks, { type });
275
- let dataUrl = URL.createObjectURL(blob);
276
-
277
- let anchor = document.createElement('a');
278
- anchor.href = dataUrl;
279
- anchor.download = fullFileName;
280
- anchor.click();
281
-
282
- URL.revokeObjectURL(dataUrl);
283
- this.chunks = [];
284
- this.resetUI();
285
- }
286
-
287
- resetUI() {
288
- this.recordPauseButton.innerHTML = '⏺';
289
- this.recordPauseButton.title = 'Start Recording';
290
-
291
- this.deleteSaveButton.innerHTML = '💾';
292
- this.deleteSaveButton.title = 'Save Recording';
293
-
294
- this.deleteSaveButton.disabled = true;
295
- this.timerDisplay.textContent = '00:00:00:00';
296
- this.elapsedTime = 0;
297
- }
298
-
299
- updateTimer() {
300
- let totalElapsed;
301
- if (q.isPaused) {
302
- totalElapsed = this.elapsedTime;
303
- } else {
304
- totalElapsed = this.elapsedTime + (Date.now() - this.startTime);
305
- }
306
- let formattedTime = this.formatTime(totalElapsed);
307
- this.timerDisplay.textContent = formattedTime;
308
- }
309
-
310
- formatTime(milliseconds) {
311
- let totalSeconds = Math.floor(milliseconds / 1000);
312
- let hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0');
313
- totalSeconds %= 3600;
314
- let minutes = String(Math.floor(totalSeconds / 60)).padStart(2, '0');
315
- let seconds = String(totalSeconds % 60).padStart(2, '0');
316
- let ms = String(Math.floor((milliseconds % 1000) / 10)).padStart(2, '0');
317
- return `${hours}:${minutes}:${seconds}:${ms}`;
318
- }
319
- }
320
-
321
- $.isRecording = false;
322
-
323
- let _rec;
324
-
325
- $.record = (videoSettings) => {
326
- if (!_rec) {
327
- _rec = new Q5Recorder($.canvas, { x: undefined, y: undefined });
328
- }
329
- if (!$.isRecording) {
330
- _rec.start(videoSettings);
331
- $.isRecording = true;
332
- } else if (_rec.isPaused) {
333
- _rec.resumeRecording();
334
- }
335
- };
336
-
337
- $.pauseRecording = () => {
338
- if (_rec && $.isRecording && !_rec.isPaused) {
339
- _rec.pauseRecording();
340
- }
341
- };
342
-
343
- $.deleteRecording = () => {
344
- if (_rec) {
345
- _rec.deleteRecording();
346
- $.isRecording = false;
347
- }
348
- };
349
-
350
- $.saveRecording = (fileName) => {
351
- if (_rec) {
352
- _rec.saveRecording(fileName);
353
- $.isRecording = false;
354
- }
355
- };
356
-
357
- $.createRecorder = (x = 10, y = 10) => {
358
- if (!_rec) {
359
- _rec = new Q5Recorder($.canvas, { x, y });
360
- } else {
361
- _rec.wrapper.style.top = `${y}px`;
362
- _rec.wrapper.style.left = `${x}px`;
363
- _rec.wrapper.style.display = 'flex';
364
- }
365
- };
366
- };
package/src/q5-sensors.js DELETED
@@ -1,98 +0,0 @@
1
- /* Adds mobile device accelerometer and gyroscope input */
2
- Q5.modules.sensors = ($) => {
3
- $.accelerationX = 0;
4
- $.accelerationY = 0;
5
- $.accelerationZ = 0;
6
- $.rotationX = 0;
7
- $.rotationY = 0;
8
- $.rotationZ = 0;
9
- $.relRotationX = 0;
10
- $.relRotationY = 0;
11
- $.relRotationZ = 0;
12
- $.pAccelerationX = 0;
13
- $.pAccelerationY = 0;
14
- $.pAccelerationZ = 0;
15
- $.pRotationX = 0;
16
- $.pRotationY = 0;
17
- $.pRotationZ = 0;
18
- $.pRelRotationX = 0;
19
- $.pRelRotationY = 0;
20
- $.pRelRotationZ = 0;
21
-
22
- $.hasSensorPermission =
23
- (!window.DeviceOrientationEvent && !window.DeviceMotionEvent) ||
24
- !(DeviceOrientationEvent.requestPermission || DeviceMotionEvent.requestPermission);
25
- $.requestSensorPermissions = () => {
26
- if (DeviceOrientationEvent.requestPermission) {
27
- DeviceOrientationEvent.requestPermission()
28
- .then((response) => {
29
- if (response == 'granted') {
30
- if (DeviceMotionEvent.requestPermission) {
31
- DeviceMotionEvent.requestPermission()
32
- .then((response) => {
33
- if (response == 'granted') {
34
- $.hasSensorPermission = true;
35
- }
36
- })
37
- .catch(alert);
38
- }
39
- }
40
- })
41
- .catch(alert);
42
- }
43
- };
44
-
45
- let ROTX = (a) => [1, 0, 0, 0, 0, $.cos(a), -$.sin(a), 0, 0, $.sin(a), $.cos(a), 0, 0, 0, 0, 1];
46
- let ROTY = (a) => [$.cos(a), 0, $.sin(a), 0, 0, 1, 0, 0, -$.sin(a), 0, $.cos(a), 0, 0, 0, 0, 1];
47
- let MULT = (A, B) => [
48
- A[0] * B[0] + A[1] * B[4] + A[2] * B[8] + A[3] * B[12],
49
- A[0] * B[1] + A[1] * B[5] + A[2] * B[9] + A[3] * B[13],
50
- A[0] * B[2] + A[1] * B[6] + A[2] * B[10] + A[3] * B[14],
51
- A[0] * B[3] + A[1] * B[7] + A[2] * B[11] + A[3] * B[15],
52
- A[4] * B[0] + A[5] * B[4] + A[6] * B[8] + A[7] * B[12],
53
- A[4] * B[1] + A[5] * B[5] + A[6] * B[9] + A[7] * B[13],
54
- A[4] * B[2] + A[5] * B[6] + A[6] * B[10] + A[7] * B[14],
55
- A[4] * B[3] + A[5] * B[7] + A[6] * B[11] + A[7] * B[15],
56
- A[8] * B[0] + A[9] * B[4] + A[10] * B[8] + A[11] * B[12],
57
- A[8] * B[1] + A[9] * B[5] + A[10] * B[9] + A[11] * B[13],
58
- A[8] * B[2] + A[9] * B[6] + A[10] * B[10] + A[11] * B[14],
59
- A[8] * B[3] + A[9] * B[7] + A[10] * B[11] + A[11] * B[15],
60
- A[12] * B[0] + A[13] * B[4] + A[14] * B[8] + A[15] * B[12],
61
- A[12] * B[1] + A[13] * B[5] + A[14] * B[9] + A[15] * B[13],
62
- A[12] * B[2] + A[13] * B[6] + A[14] * B[10] + A[15] * B[14],
63
- A[12] * B[3] + A[13] * B[7] + A[14] * B[11] + A[15] * B[15]
64
- ];
65
- let TRFM = (A, v) => [
66
- (A[0] * v[0] + A[1] * v[1] + A[2] * v[2] + A[3]) / (A[12] * v[0] + A[13] * v[1] + A[14] * v[2] + A[15]),
67
- (A[4] * v[0] + A[5] * v[1] + A[6] * v[2] + A[7]) / (A[12] * v[0] + A[13] * v[1] + A[14] * v[2] + A[15]),
68
- (A[8] * v[0] + A[9] * v[1] + A[10] * v[2] + A[11]) / (A[12] * v[0] + A[13] * v[1] + A[14] * v[2] + A[15])
69
- ];
70
-
71
- window.addEventListener('deviceorientation', (e) => {
72
- $.pRotationX = $.rotationX;
73
- $.pRotationY = $.rotationY;
74
- $.pRotationZ = $.rotationZ;
75
- $.pRelRotationX = $.relRotationX;
76
- $.pRelRotationY = $.relRotationY;
77
- $.pRelRotationZ = $.relRotationZ;
78
-
79
- $.rotationX = e.beta * (Math.PI / 180.0);
80
- $.rotationY = e.gamma * (Math.PI / 180.0);
81
- $.rotationZ = e.alpha * (Math.PI / 180.0);
82
- $.relRotationX = [-$.rotationY, -$.rotationX, $.rotationY][Math.trunc(window.orientation / 90) + 1];
83
- $.relRotationY = [-$.rotationX, $.rotationY, $.rotationX][Math.trunc(window.orientation / 90) + 1];
84
- $.relRotationZ = $.rotationZ;
85
- });
86
-
87
- window.addEventListener('devicemotion', (e) => {
88
- $.pAccelerationX = $.accelerationX;
89
- $.pAccelerationY = $.accelerationY;
90
- $.pAccelerationZ = $.accelerationZ;
91
- if (!e.acceleration) {
92
- let grav = TRFM(MULT(ROTY($.rotationY), ROTX($.rotationX)), [0, 0, -9.80665]);
93
- $.accelerationX = e.accelerationIncludingGravity.x + grav[0];
94
- $.accelerationY = e.accelerationIncludingGravity.y + grav[1];
95
- $.accelerationZ = e.accelerationIncludingGravity.z - grav[2];
96
- }
97
- });
98
- };
package/src/q5-sound.js DELETED
@@ -1,64 +0,0 @@
1
- Q5.modules.sound = ($, q) => {
2
- $.Sound = Q5.Sound;
3
-
4
- let sounds = [];
5
-
6
- $.loadSound = (path, cb) => {
7
- q._preloadCount++;
8
- let s = new Q5.Sound(path, cb);
9
- s.crossOrigin = 'Anonymous';
10
- s.addEventListener('canplaythrough', () => {
11
- if (!s.loaded) {
12
- q._preloadCount--;
13
- s.loaded = true;
14
- if (Q5.aud) s.init();
15
- if (cb) cb(s);
16
- }
17
- });
18
- sounds.push(s);
19
- return s;
20
- };
21
- $.getAudioContext = () => Q5.aud;
22
- $.userStartAudio = () => {
23
- if (window.AudioContext) {
24
- if (!Q5.aud) {
25
- Q5.aud = new window.AudioContext();
26
- for (let s of sounds) s.init();
27
- }
28
- return Q5.aud.resume();
29
- }
30
- };
31
- };
32
-
33
- if (window.Audio) {
34
- Q5.Sound ??= class extends Audio {
35
- init() {
36
- let s = this;
37
- s.panner = Q5.aud.createStereoPanner();
38
- s.source = Q5.aud.createMediaElementSource(s);
39
- s.source.connect(s.panner);
40
- s.panner.connect(Q5.aud.destination);
41
- let pan = s.pan;
42
- Object.defineProperty(s, 'pan', {
43
- get: () => s.panner.pan.value,
44
- set: (v) => (s.panner.pan.value = v)
45
- });
46
- if (pan) s.pan = pan;
47
- }
48
- setVolume(level) {
49
- this.volume = level;
50
- }
51
- setLoop(loop) {
52
- this.loop = loop;
53
- }
54
- setPan(value) {
55
- this.pan = value;
56
- }
57
- isLoaded() {
58
- return this.loaded;
59
- }
60
- isPlaying() {
61
- return !this.paused;
62
- }
63
- };
64
- }
package/src/q5-util.js DELETED
@@ -1,50 +0,0 @@
1
- Q5.modules.util = ($, q) => {
2
- $._loadFile = (path, cb, type) => {
3
- q._preloadCount++;
4
- let ret = {};
5
- fetch(path)
6
- .then((r) => {
7
- if (type == 'json') return r.json();
8
- return r.text();
9
- })
10
- .then((r) => {
11
- q._preloadCount--;
12
- if (type == 'csv') r = $.CSV.parse(r);
13
- Object.assign(ret, r);
14
- if (cb) cb(r);
15
- });
16
- return ret;
17
- };
18
-
19
- $.loadText = (path, cb) => $._loadFile(path, cb, 'text');
20
- $.loadJSON = (path, cb) => $._loadFile(path, cb, 'json');
21
- $.loadCSV = (path, cb) => $._loadFile(path, cb, 'csv');
22
-
23
- $.CSV = {};
24
- $.CSV.parse = (csv, sep = ',', lineSep = '\n') => {
25
- if (!csv.length) return [];
26
- let a = [],
27
- lns = csv.split(lineSep),
28
- headers = lns[0].split(sep).map((h) => h.replaceAll('"', ''));
29
- for (let i = 1; i < lns.length; i++) {
30
- let o = {},
31
- ln = lns[i].split(sep);
32
- headers.forEach((h, i) => (o[h] = JSON.parse(ln[i])));
33
- a.push(o);
34
- }
35
- return a;
36
- };
37
-
38
- if (typeof localStorage == 'object') {
39
- $.storeItem = localStorage.setItem;
40
- $.getItem = localStorage.getItem;
41
- $.removeItem = localStorage.removeItem;
42
- $.clearStorage = localStorage.clear;
43
- }
44
-
45
- $.year = () => new Date().getFullYear();
46
- $.day = () => new Date().getDay();
47
- $.hour = () => new Date().getHours();
48
- $.minute = () => new Date().getMinutes();
49
- $.second = () => new Date().getSeconds();
50
- };