@cocreate/elements 1.33.2 → 1.34.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/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/src/fetchSrc.js +193 -30
- package/src/form.js +1 -1
- package/src/index.js +20 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# [1.34.0](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.33.2...v1.34.0) (2024-02-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add audio to src ignore ([80ce64c](https://github.com/CoCreate-app/CoCreate-elements/commit/80ce64cae35a0e6d0932beccebbecc69babb143a))
|
|
7
|
+
* crud="false" ([6c92490](https://github.com/CoCreate-app/CoCreate-elements/commit/6c92490ab389a1813e10cfd1207b2061b8ef0ce1))
|
|
8
|
+
* getValue using getAttribute() in order to handle operators ([798227d](https://github.com/CoCreate-app/CoCreate-elements/commit/798227d28efc1613b2ce4b90012131e990c8ab60))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* $length operator for for key ([777cf3b](https://github.com/CoCreate-app/CoCreate-elements/commit/777cf3bd4ee2ac8031d890d1df21879df1aa22e6))
|
|
14
|
+
* stream="" attribute dynimaclly handles streaming ([67d0bbb](https://github.com/CoCreate-app/CoCreate-elements/commit/67d0bbba0c119b86ee095a78dc093c047758b91a))
|
|
15
|
+
|
|
1
16
|
## [1.33.2](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.33.1...v1.33.2) (2024-01-30)
|
|
2
17
|
|
|
3
18
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/elements",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0",
|
|
4
4
|
"description": "Fetch content into any html element by using html5 attributes to query data stored in CoCreate headless CMS. If data is changed it will instantly reflect in html tags as changes are broadcasted in realtime",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"elements",
|
package/src/fetchSrc.js
CHANGED
|
@@ -1,51 +1,215 @@
|
|
|
1
1
|
import observer from '@cocreate/observer';
|
|
2
2
|
|
|
3
|
-
const selector = '[src]:not(img, video, script, input, iframe, frame, link, source)'
|
|
3
|
+
const selector = '[src]:not(img, video, audio, script, input, iframe, frame, link, source), [source], [stream]'
|
|
4
4
|
const initializing = new Map();
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
function init() {
|
|
8
|
-
|
|
7
|
+
function init(elements) {
|
|
8
|
+
if (elements && !Array.isArray(elements))
|
|
9
|
+
elements = [elements]
|
|
10
|
+
|
|
11
|
+
if (!elements)
|
|
12
|
+
elements = document.querySelectorAll(selector);
|
|
13
|
+
|
|
9
14
|
initElements(elements);
|
|
10
15
|
};
|
|
11
16
|
|
|
12
|
-
function initElements(elements) {
|
|
13
|
-
for (let element of elements)
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
async function initElements(elements) {
|
|
18
|
+
for (let element of elements) {
|
|
19
|
+
let src = element.getAttribute('src') || element.getAttribute('source') || element.getAttribute('stream');
|
|
20
|
+
|
|
21
|
+
if (!src || /{{\s*([\w\W]+)\s*}}/g.test(src))
|
|
22
|
+
return;
|
|
23
|
+
|
|
24
|
+
let initialize = initializing.get(element)
|
|
25
|
+
if (!initialize || initialize.src != src) {
|
|
26
|
+
initializing.set(element, { src });
|
|
27
|
+
if (src) {
|
|
28
|
+
try {
|
|
29
|
+
let response = await fetch(src);
|
|
30
|
+
if (element.tagName === 'AUDIO' || element.tagName === 'VIDEO') {
|
|
31
|
+
try {
|
|
32
|
+
let mediaConfig = await response.json();
|
|
33
|
+
let sourceBuffer;
|
|
34
|
+
|
|
35
|
+
const mediaSource = new MediaSource();
|
|
36
|
+
video.src = URL.createObjectURL(mediaSource);
|
|
37
|
+
mediaSource.addEventListener('sourceopen', async () => {
|
|
38
|
+
const contentType = response.headers.get("Content-Type");
|
|
39
|
+
sourceBuffer = mediaSource.addSourceBuffer(contentType);
|
|
40
|
+
|
|
41
|
+
sourceBuffer.addEventListener('updateend', () => {
|
|
42
|
+
if (!sourceBuffer.updating && mediaSource.readyState === 'open') {
|
|
43
|
+
mediaSource.endOfStream();
|
|
44
|
+
|
|
45
|
+
video.addEventListener('ended', () => {
|
|
46
|
+
URL.revokeObjectURL(video.src);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
16
51
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
// Event listener for seeking
|
|
53
|
+
element.addEventListener('seeking', () => {
|
|
54
|
+
let currentTime = element.currentTime;
|
|
55
|
+
let chunkIndex = Math.floor(currentTime / chunkDuration);
|
|
56
|
+
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Event listener for seeked
|
|
60
|
+
element.addEventListener('seeked', () => {
|
|
61
|
+
let currentTime = element.currentTime;
|
|
62
|
+
if (isBuffered(element, currentTime))
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
let query = {
|
|
66
|
+
"start": { $lte: currentTime },
|
|
67
|
+
"end": { $gte: currentTime }
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
let segments = queryData(mediaConfig.segments, query);
|
|
71
|
+
for (let segment of segments) {
|
|
72
|
+
getSegment(sourceBuffer, segment)
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
element.addEventListener('timeupdate', function () {
|
|
77
|
+
const bufferEnd = video.buffered.end(0);
|
|
78
|
+
const currentTime = video.currentTime;
|
|
79
|
+
const threshold = 10; // seconds before buffer end to fetch the next segment
|
|
80
|
+
|
|
81
|
+
if (bufferEnd - currentTime < threshold) {
|
|
82
|
+
// Time to fetch the next segment
|
|
83
|
+
getSegment(sourceBuffer, mediaConfig)
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
} catch (error) {
|
|
87
|
+
video.src = src
|
|
88
|
+
// let blob = await response.json();
|
|
89
|
+
// URL.createObjectURL(blob);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
let text = await response.text();
|
|
93
|
+
if (text) {
|
|
94
|
+
let path = element.getAttribute('path')
|
|
95
|
+
if (path)
|
|
96
|
+
text = text.replaceAll('{{path}}', path)
|
|
97
|
+
element.setValue(text);
|
|
98
|
+
initializing.delete(element)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
console.log('FetchSrc error:' + err);
|
|
35
103
|
}
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.log('FetchSrc error:' + err);
|
|
38
104
|
}
|
|
39
105
|
}
|
|
40
106
|
}
|
|
41
107
|
};
|
|
42
108
|
|
|
109
|
+
async function getSegment(sourceBuffer, segmentConfig) {
|
|
110
|
+
// TODO: use socket/crud/file with room using the url to get one or more segments and append
|
|
111
|
+
let segments = []
|
|
112
|
+
|
|
113
|
+
if (segmentConfig.array && segmentConfig.object) {
|
|
114
|
+
let data = { method: 'object.read', ...segmentConfig }
|
|
115
|
+
data = await crud.send(data)
|
|
116
|
+
if (segmentConfig.key) {
|
|
117
|
+
// TODO: utils.getValuefromObject
|
|
118
|
+
if (Array.isArray(data.object[0][key]))
|
|
119
|
+
segments.push(...data.object[0][key])
|
|
120
|
+
else
|
|
121
|
+
segments.push(data.object[0][key])
|
|
122
|
+
} else
|
|
123
|
+
segments.push(...data.object)
|
|
124
|
+
} else if (segmentConfig.src) {
|
|
125
|
+
data = await fetch(src);
|
|
126
|
+
}
|
|
127
|
+
appendSegment(sourceBuffer, segment)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function appendSegment(sourceBuffer, segment) {
|
|
131
|
+
let arrayBuffer = segment.src
|
|
132
|
+
if (typeof segment.src === 'string') {
|
|
133
|
+
try {
|
|
134
|
+
arrayBuffer = base64ToArrayBuffer(segment.src);
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.error("Base64 decode error:", e);
|
|
137
|
+
}
|
|
138
|
+
} else if (segment.src instanceof ArrayBuffer) {
|
|
139
|
+
arrayBuffer = segment.src
|
|
140
|
+
} else if (segment.src instanceof Blob) {
|
|
141
|
+
arrayBuffer = await segment.src.arrayBuffer();
|
|
142
|
+
} else {
|
|
143
|
+
console.error("Unhandled segment data type");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Wait for the source buffer to be ready for updates
|
|
147
|
+
if (sourceBuffer.updating) await new Promise(resolve => sourceBuffer.addEventListener('updateend', resolve, { once: true }));
|
|
148
|
+
|
|
149
|
+
sourceBuffer.appendBuffer(arrayBuffer);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isBuffered(element, currentTime) {
|
|
153
|
+
for (let i = 0; i < element.buffered.length; i++) {
|
|
154
|
+
if (currentTime >= element.buffered.start(i) && currentTime <= element.buffered.end(i)) {
|
|
155
|
+
return true; // The currentTime is within a buffered range
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return false; // The currentTime is not buffered
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function base64ToBlob(base64, contentType = '') {
|
|
162
|
+
const binaryString = atob(base64.split(',')[1]);
|
|
163
|
+
const len = binaryString.length;
|
|
164
|
+
const uint8Array = new Uint8Array(len);
|
|
165
|
+
for (let i = 0; i < len; i++) {
|
|
166
|
+
uint8Array[i] = binaryString.charCodeAt(i);
|
|
167
|
+
}
|
|
168
|
+
return new Blob([uint8Array], { type: contentType });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function base64ToArrayBuffer(base64) {
|
|
172
|
+
const binaryString = window.atob(base64.split(',')[1]); // Decode base64
|
|
173
|
+
const len = binaryString.length;
|
|
174
|
+
const bytes = new Uint8Array(len);
|
|
175
|
+
for (let i = 0; i < len; i++) {
|
|
176
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
177
|
+
}
|
|
178
|
+
return bytes.buffer; // Return ArrayBuffer
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const videoStreamConfig = {
|
|
182
|
+
title: "Video Title",
|
|
183
|
+
totalDuration: 3600, // Total duration of the video in seconds
|
|
184
|
+
'content-type': "video/mp4",
|
|
185
|
+
segments: [
|
|
186
|
+
{
|
|
187
|
+
_id: 1,
|
|
188
|
+
src: "http://example.com/video/chunk1.mp4", // src stored using file path
|
|
189
|
+
storage: '', // src stored using crud path
|
|
190
|
+
array: '', // src stored using crud path
|
|
191
|
+
object: '', // src stored using crud path
|
|
192
|
+
key: '', // src stored using crud path
|
|
193
|
+
start: 0, // Start time of this chunk in seconds
|
|
194
|
+
end: 10, // End time of this chunk in seconds
|
|
195
|
+
duration: 10,
|
|
196
|
+
codec: "h.264",
|
|
197
|
+
resolution: {
|
|
198
|
+
width: 1920,
|
|
199
|
+
height: 1080
|
|
200
|
+
},
|
|
201
|
+
bitrate: "5000kbps",
|
|
202
|
+
},
|
|
203
|
+
// Repeat for each chunk
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
|
|
43
207
|
observer.init({
|
|
44
208
|
name: 'CoCreateSrc',
|
|
45
209
|
observe: ['addedNodes'],
|
|
46
210
|
target: selector,
|
|
47
211
|
callback: function (mutation) {
|
|
48
|
-
|
|
212
|
+
init(mutation.target);
|
|
49
213
|
}
|
|
50
214
|
});
|
|
51
215
|
|
|
@@ -55,9 +219,8 @@ observer.init({
|
|
|
55
219
|
attributeName: ['src'],
|
|
56
220
|
target: selector,
|
|
57
221
|
callback: function (mutation) {
|
|
58
|
-
|
|
222
|
+
init(mutation.target);
|
|
59
223
|
}
|
|
60
224
|
});
|
|
61
225
|
|
|
62
226
|
init()
|
|
63
|
-
|
package/src/form.js
CHANGED
|
@@ -66,7 +66,7 @@ function setAttribute(form) {
|
|
|
66
66
|
for (let el of elements) {
|
|
67
67
|
// Set the value of the attribute.
|
|
68
68
|
if (!el.getAttribute(attribute.name)) {
|
|
69
|
-
el.setAttribute(attribute.name, attribute.
|
|
69
|
+
el.setAttribute(attribute.name, form.getAttribute(attribute.name));
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
package/src/index.js
CHANGED
|
@@ -62,7 +62,7 @@ async function init(element) {
|
|
|
62
62
|
|
|
63
63
|
let dataObjects = new Map();
|
|
64
64
|
for (let i = 0; i < element.length; i++) {
|
|
65
|
-
if (elements.has(element[i]) || element[i].tagName === 'FORM')
|
|
65
|
+
if (elements.has(element[i]) || element[i].tagName === 'FORM' || element[i].getAttribute('crud') === 'false')
|
|
66
66
|
continue
|
|
67
67
|
|
|
68
68
|
let data = await initElement(element[i]);
|
|
@@ -346,14 +346,25 @@ async function setData(element, data) {
|
|
|
346
346
|
if (el.getFilter || el.renderValue)
|
|
347
347
|
await filterData(el, data, type, key)
|
|
348
348
|
else {
|
|
349
|
-
let value = CRUD.getValueFromObject(data[type][0], key);
|
|
350
349
|
if (key) {
|
|
350
|
+
let value
|
|
351
|
+
if (key.includes('$length')) {
|
|
352
|
+
value = CRUD.getValueFromObject(data[type][0], key.replace(/\.\$length$/, ''))
|
|
353
|
+
if (value)
|
|
354
|
+
value = value.length
|
|
355
|
+
else
|
|
356
|
+
value = 0
|
|
357
|
+
} else {
|
|
358
|
+
value = CRUD.getValueFromObject(data[type][0], key)
|
|
359
|
+
}
|
|
360
|
+
|
|
351
361
|
if (!data[type].length) continue;
|
|
352
362
|
if (isRead == "false" || isCrdt == "true") continue;
|
|
353
363
|
if (isListen == "false" && !data.method.endsWith('.read')) continue;
|
|
364
|
+
// TODO: object.update data returned from server will not include $operator
|
|
365
|
+
el.setValue(value);
|
|
366
|
+
|
|
354
367
|
}
|
|
355
|
-
// TODO: object.update data returned from server will not include $operator
|
|
356
|
-
el.setValue(value);
|
|
357
368
|
}
|
|
358
369
|
}
|
|
359
370
|
}
|
|
@@ -361,7 +372,8 @@ async function setData(element, data) {
|
|
|
361
372
|
async function filterData(element, data, type, key) {
|
|
362
373
|
let operator = '', value
|
|
363
374
|
|
|
364
|
-
if (key && key !== '$length') {
|
|
375
|
+
// if (key && key !== '$length') {
|
|
376
|
+
if (key && !key.includes('$length')) {
|
|
365
377
|
if (!data || !type) return
|
|
366
378
|
|
|
367
379
|
if (key.startsWith('$')) {
|
|
@@ -410,6 +422,9 @@ async function filterData(element, data, type, key) {
|
|
|
410
422
|
await element.renderValue(data);
|
|
411
423
|
} else if (key === '$length') {
|
|
412
424
|
element.setValue(data[type].length);
|
|
425
|
+
} else if (key.includes('$length')) {
|
|
426
|
+
let value = CRUD.getValueFromObject(data[type][0], key.replace(/\.\$length$/, ''))
|
|
427
|
+
element.setValue(value.length);
|
|
413
428
|
} else if (data)
|
|
414
429
|
element.setValue(data)
|
|
415
430
|
|