@cocreate/elements 1.33.2 → 1.34.1

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 CHANGED
@@ -1,3 +1,25 @@
1
+ ## [1.34.1](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.34.0...v1.34.1) (2024-02-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * realtime defaults to false ([83d2787](https://github.com/CoCreate-app/CoCreate-elements/commit/83d2787da3e2d3386f379c692852d10f8b15f16e))
7
+
8
+ # [1.34.0](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.33.2...v1.34.0) (2024-02-13)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add audio to src ignore ([80ce64c](https://github.com/CoCreate-app/CoCreate-elements/commit/80ce64cae35a0e6d0932beccebbecc69babb143a))
14
+ * crud="false" ([6c92490](https://github.com/CoCreate-app/CoCreate-elements/commit/6c92490ab389a1813e10cfd1207b2061b8ef0ce1))
15
+ * getValue using getAttribute() in order to handle operators ([798227d](https://github.com/CoCreate-app/CoCreate-elements/commit/798227d28efc1613b2ce4b90012131e990c8ab60))
16
+
17
+
18
+ ### Features
19
+
20
+ * $length operator for for key ([777cf3b](https://github.com/CoCreate-app/CoCreate-elements/commit/777cf3bd4ee2ac8031d890d1df21879df1aa22e6))
21
+ * stream="" attribute dynimaclly handles streaming ([67d0bbb](https://github.com/CoCreate-app/CoCreate-elements/commit/67d0bbba0c119b86ee095a78dc093c047758b91a))
22
+
1
23
  ## [1.33.2](https://github.com/CoCreate-app/CoCreate-elements/compare/v1.33.1...v1.33.2) (2024-01-30)
2
24
 
3
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/elements",
3
- "version": "1.33.2",
3
+ "version": "1.34.1",
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
- var elements = document.querySelectorAll(selector);
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
- initElement(element);
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
- async function initElement(element) {
18
- let src = element.getAttribute('src');
19
-
20
- if (!src || /{{\s*([\w\W]+)\s*}}/g.test(src))
21
- return;
22
- let initialize = initializing.get(element)
23
- if (!initialize || initialize.src != src) {
24
- initializing.set(element, { src });
25
- if (src) {
26
- try {
27
- let response = await fetch(src);
28
- let text = await response.text();
29
- if (text) {
30
- let path = element.getAttribute('path')
31
- if (path)
32
- text = text.replaceAll('{{path}}', path)
33
- element.setValue(text);
34
- initializing.delete(element)
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
- initElement(mutation.target);
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
- initElement(mutation.target);
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.value);
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]);
@@ -94,7 +94,7 @@ async function initElement(el) {
94
94
  || el.contenteditable) {
95
95
  el.addEventListener('input', function (e) {
96
96
  const { object, key, isRealtime, isCrdt } = getAttributes(el);
97
- if (isRealtime == "false" || key == "_id") return;
97
+ if (!isRealtime || isRealtime === "false" || key === "_id") return;
98
98
  if (isCrdt == "true" && object && object !== 'pending') return
99
99
  if (e.detail && e.detail.skip == true) return;
100
100
  if (data.type !== 'object' && data[data.type] === 'pending') return
@@ -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 && 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