smsmslib 1.0.38 → 1.0.41

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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @file
3
+ * Implementation of the AvControllerState_t class.
4
+ *
5
+ * @description
6
+ * This module defines the data structure representing the playback state
7
+ * of an AV controller. It encapsulates essential properties from the
8
+ * HTMLMediaElement to be used as the shared state within the Observer
9
+ * pattern, facilitating synchronization between the media element and
10
+ * its associated UI components.
11
+ *
12
+ * @module src/av_controller/AvControllerState_t
13
+ */
14
+
15
+
16
+ /**
17
+ * Av controller status.
18
+ */
19
+ export class AvControllerState_t
20
+ {
21
+ /** The initial start time of the media in seconds. */
22
+ d_start_sec;
23
+
24
+ /** The initial end time of the media in seconds.
25
+ * A negative value indicates that the end time is invalid or indefinite
26
+ * (e.g., live streaming).
27
+ */
28
+ d_end_sec;
29
+
30
+ /** The total duration of the media in seconds.
31
+ * A negative value indicates that the duration is unavailable or infinite.
32
+ */
33
+ duration;
34
+
35
+ /** HTMLMediaElement.paused (Read only)
36
+ * A boolean that indicates whether the media element is paused. */
37
+ paused;
38
+
39
+ /** HTMLMediaElement.playbackRate
40
+ * A double that indicates the rate at which the media is being played back. */
41
+ playbackRate;
42
+
43
+ /** HTMLMediaElement.currentTime
44
+ * A double-precision floating-point value indicating the current playback
45
+ * time in seconds. */
46
+ currentTime;
47
+
48
+ /** Element.clientWidth (of the outer container)
49
+ * The maximum width of the progress bar background in pixels.
50
+ * This represents the total length of the seek bar. */
51
+ i_bar_px_max;
52
+
53
+ constructor()
54
+ {
55
+ this.d_start_sec = 0.0;
56
+ this.d_end_sec = 10.0;
57
+ this.duration = 10.0;
58
+ this.paused = true;
59
+ this.playbackRate = 1.0;
60
+ this.currentTime = this.d_start_sec;
61
+ this.i_bar_px_max = 10;
62
+ }
63
+
64
+ }
65
+
66
+
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @file
3
+ * Implementation of the HtmlControl_t class.
4
+ *
5
+ * @description
6
+ * This module provides the HtmlControl_t class, which manages the
7
+ * visibility of the AV custom control container. It acts as an
8
+ * observer linked to a specific DOM element and
9
+ * ensures the UI is revealed only when the media state is initialized.
10
+ *
11
+ * @module src/HtmlControl_t
12
+ */
13
+
14
+
15
+ /** ---------------------------------------------------------------------------
16
+ * Imports.
17
+ * --------------------------------------------------------------------------- */
18
+
19
+ import {HtmlElement_t} from "../system/index.js";
20
+
21
+
22
+ /** ---------------------------------------------------------------------------
23
+ * Class.
24
+ * --------------------------------------------------------------------------- */
25
+
26
+ /**
27
+ * Manages the visibility and lifecycle of the custom AV control interface.
28
+ *
29
+ * This class serves as the container for custom playback UI elements.
30
+ */
31
+ export class HtmlControl_t extends HtmlElement_t
32
+ {
33
+ /**
34
+ * @see HtmlElement_t#constructor
35
+ */
36
+ constructor(s_selector)
37
+ {
38
+ super(s_selector);
39
+ }
40
+
41
+ /**
42
+ * Synchronizes the initial state to display the custom controls.
43
+ *
44
+ * @param {AvControllerState_t} o_state [in]
45
+ * The initial data state provided by the subject.
46
+ *
47
+ * @see Observer_t#handle_initial_update
48
+ */
49
+ handle_initial_update(o_state)
50
+ {
51
+ this._o_element.style.visibility = "visible"; /* Show custom UI for playback. */
52
+ }
53
+
54
+ }
55
+
56
+
@@ -0,0 +1,515 @@
1
+ /**
2
+ * @file
3
+ * Implementation of the HtmlMedia_t class and its associated helper functions.
4
+ *
5
+ * @description
6
+ * This module provides the HtmlMedia_t class, which serves as a specialized
7
+ * observer for HTMLMediaElements (e.g., <video> or <audio>). It facilitates
8
+ * bidirectional synchronization:
9
+ *
10
+ * @module src/av_controller/HtmlMedia_t
11
+ */
12
+
13
+ /** ---------------------------------------------------------------------------
14
+ * Imports.
15
+ * --------------------------------------------------------------------------- */
16
+
17
+ import
18
+ {
19
+ sj_is_number,
20
+ sj_is_object,
21
+ sj_prop_chg_own,
22
+ HtmlElement_t,
23
+ sj_element_key_get,
24
+ sj_Subject_get,
25
+ Subject_t
26
+ }
27
+ from "../system/index.js";
28
+
29
+ import {AvControllerState_t} from "./AvControllerState_t.js"
30
+
31
+
32
+ /** ---------------------------------------------------------------------------
33
+ * Class.
34
+ * --------------------------------------------------------------------------- */
35
+
36
+ /**
37
+ * Synchronizes a media element (e.g., <video> or <audio>) with the application
38
+ * state.
39
+ *
40
+ * @extends HtmlElement_t
41
+ */
42
+ export class HtmlMedia_t extends HtmlElement_t
43
+ {
44
+ /**
45
+ * @see HtmlElement_t#constructor
46
+ */
47
+ constructor(s_selector)
48
+ {
49
+ super(s_selector);
50
+
51
+ const b_valid = this.is_valid();
52
+
53
+ if (b_valid)
54
+ {
55
+ const o_element = this._o_element;
56
+
57
+ o_element.addEventListener("durationchange", HtmlMedia_on_durationchange);
58
+ o_element.addEventListener("timeupdate" , HtmlMedia_on_timeupdate);
59
+ o_element.addEventListener("ended" , HtmlMedia_on_ended);
60
+ o_element.addEventListener("loadedmetadata", HtmlMedia_on_loadedmetadata);
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @see HtmlMedia_loadedmetadata_manual
66
+ */
67
+ loadedmetadata_manual()
68
+ {
69
+ const i_exe = HtmlMedia_loadedmetadata_manual(this._o_element);
70
+
71
+ return i_exe;
72
+ }
73
+
74
+ /**
75
+ * @see Observer_t#handle_retrieve
76
+ * @see HtmlMedia_handle_retrieve
77
+ */
78
+ handle_retrieve(o_state)
79
+ {
80
+ HtmlMedia_handle_retrieve(this._o_element, o_state);
81
+ }
82
+
83
+ /**
84
+ * @see Observer_t#handle_initial_update
85
+ * @see HtmlMedia_handle_initial_update
86
+ */
87
+ handle_initial_update(o_state)
88
+ {
89
+ HtmlMedia_handle_initial_update(this._o_element, o_state)
90
+ }
91
+
92
+ /**
93
+ * @see Observer_t#handle_update
94
+ * @see HtmlMedia_handle_update
95
+ */
96
+ handle_update(o_state, o_chg, o_src)
97
+ {
98
+ HtmlMedia_handle_update(this._o_element, o_state, o_chg, o_src);
99
+ }
100
+
101
+ }
102
+
103
+
104
+ /** ---------------------------------------------------------------------------
105
+ * Functions.
106
+ * --------------------------------------------------------------------------- */
107
+
108
+ /**
109
+ * Event handler for the media element's 'loadedmetadata' event.
110
+ *
111
+ * @param {Event} o_event [in]
112
+ * The event object dispatched by the media element.
113
+ *
114
+ * @see HtmlMedia_loadedmetadata
115
+ */
116
+ function HtmlMedia_on_loadedmetadata(o_event)
117
+ {
118
+ HtmlMedia_loadedmetadata(o_event.currentTarget);
119
+ }
120
+
121
+
122
+ /**
123
+ * Event handler for the media element's 'timeupdate' event.
124
+ *
125
+ * This function synchronizes the current playback time of the media element
126
+ * with the application state.
127
+ *
128
+ * @param {Event} o_event [in]
129
+ * The event object dispatched by the media element.
130
+ */
131
+ function HtmlMedia_on_timeupdate(o_event)
132
+ {
133
+ const o_element = o_event.currentTarget;
134
+ const s_key = sj_element_key_get(o_element);
135
+ const o_Subject = sj_Subject_get(s_key);
136
+ let o_state = undefined;
137
+
138
+ if (o_Subject instanceof Subject_t)
139
+ {
140
+ o_state = o_Subject.data_get();
141
+ }
142
+
143
+ if (o_state instanceof AvControllerState_t)
144
+ {
145
+ const d_start_sec = HtmlMedia_start_sec(o_element);
146
+ const d_end_sec = HtmlMedia_end_sec(o_element);
147
+ const o_chg = {};
148
+
149
+ sj_prop_chg_own(o_state, "currentTime", o_element.currentTime, o_chg);
150
+ sj_prop_chg_own(o_state, "d_start_sec", d_start_sec , o_chg);
151
+ sj_prop_chg_own(o_state, "d_end_sec" , d_end_sec , o_chg);
152
+
153
+ if (0 < Object.keys(o_chg).length)
154
+ {
155
+ o_Subject.observer_update(o_chg, o_element);
156
+ }
157
+ }
158
+ }
159
+
160
+
161
+ /**
162
+ * Handles the 'ended' event of the media element.
163
+ * This handler synchronizes the application state when the media reaches the end.
164
+ * Since browsers automatically pause the video/audio element when it finishes,
165
+ * this function explicitly updates the `paused` state to `true` in the subject's
166
+ * data to ensure consistency between the UI and the media element.
167
+ *
168
+ * @param {Event} o_event [in]
169
+ * The event object dispatched by the media element (HTMLMediaElement).
170
+ */
171
+ function HtmlMedia_on_ended(o_event)
172
+ {
173
+ const o_element = o_event.currentTarget;
174
+ const s_key = sj_element_key_get(o_element);
175
+ const o_Subject = sj_Subject_get(s_key);
176
+ let o_state = undefined;
177
+
178
+ if (o_Subject instanceof Subject_t)
179
+ {
180
+ o_state = o_Subject.data_get();
181
+ }
182
+
183
+ if (o_state instanceof AvControllerState_t)
184
+ {
185
+ const o_chg = {};
186
+
187
+ sj_prop_chg_own(o_state, "paused", true, o_chg);
188
+
189
+ if (0 < Object.keys(o_chg).length)
190
+ {
191
+ o_Subject.observer_update(o_chg, o_element);
192
+ }
193
+ }
194
+ }
195
+
196
+
197
+ /**
198
+ * Handles the 'durationchange' event for an HTML media element.
199
+ *
200
+ * @description
201
+ * This event handler synchronizes the state of an `AvControllerState_t` object with
202
+ * the updated timing information of the media element.
203
+ *
204
+ * @param {Event} o_event [in]
205
+ * The 'durationchange' event object. `o_event.currentTarget` is expected to be
206
+ * an `HTMLMediaElement`.
207
+ *
208
+ * @returns {void}
209
+ */
210
+ function HtmlMedia_on_durationchange(o_event)
211
+ {
212
+ const o_element = o_event.currentTarget;
213
+ const s_key = sj_element_key_get(o_element);
214
+ const o_Subject = sj_Subject_get(s_key);
215
+ let o_state = undefined;
216
+
217
+ if (o_Subject instanceof Subject_t)
218
+ {
219
+ o_state = o_Subject.data_get();
220
+ }
221
+
222
+ if (o_state instanceof AvControllerState_t)
223
+ {
224
+ const duration = HtmlMedia_duration(o_element);
225
+ const d_start_sec = HtmlMedia_start_sec(o_element);
226
+ const d_end_sec = HtmlMedia_end_sec(o_element);
227
+ const o_chg = {};
228
+
229
+ sj_prop_chg_own(o_state, "duration" , duration , o_chg);
230
+ sj_prop_chg_own(o_state, "d_start_sec", d_start_sec, o_chg);
231
+ sj_prop_chg_own(o_state, "d_end_sec" , d_end_sec , o_chg);
232
+
233
+ if (0 < Object.keys(o_chg).length)
234
+ {
235
+ o_Subject.observer_update(o_chg, o_element);
236
+ }
237
+ }
238
+ }
239
+
240
+
241
+ /**
242
+ * Retrieves the initial timing information from the media element.
243
+ *
244
+ * @param {HTMLMediaElement} o_element [in]
245
+ * The source media element to read timing data from.
246
+ *
247
+ * @param {AvControllerState_t} o_state [out]
248
+ * The state object to be populated with the retrieved timing values.
249
+ */
250
+ function HtmlMedia_handle_retrieve(o_element, o_state)
251
+ {
252
+ if (o_element && (o_state instanceof AvControllerState_t))
253
+ {
254
+ o_state.duration = HtmlMedia_duration(o_element);
255
+ o_state.d_start_sec = HtmlMedia_start_sec(o_element);
256
+ o_state.d_end_sec = HtmlMedia_end_sec(o_element);
257
+ o_state.currentTime = o_state.d_start_sec;
258
+ o_state.playbackRate = 1.0;
259
+ }
260
+ }
261
+
262
+
263
+ /**
264
+ * Performs the initial setup and state synchronization for the media element.
265
+ *
266
+ * @param {HTMLMediaElement} o_element [in/out]
267
+ * The target media element to be initialized.
268
+ *
269
+ * @param {AvControllerState_t} o_state [in]
270
+ * The initial state object containing the desired playback configuration.
271
+ */
272
+ function HtmlMedia_handle_initial_update(o_element, o_state)
273
+ {
274
+ if (o_element && (o_state instanceof AvControllerState_t))
275
+ {
276
+ o_element.removeAttribute("controls"); /* Hide default UI for playback */
277
+
278
+ if (o_state.paused)
279
+ {
280
+ o_element.pause();
281
+ }
282
+ else
283
+ {
284
+ o_element.play();
285
+ }
286
+
287
+ o_element.playbackRate = o_state.playbackRate;
288
+ o_element.currentTime = o_state.d_start_sec;
289
+ }
290
+ }
291
+
292
+
293
+ /**
294
+ * Synchronizes the media element's playback state and rate based on state changes.
295
+ *
296
+ * @param {HTMLMediaElement} o_element [in/out]
297
+ * The target media element (video or audio) to be controlled.
298
+ *
299
+ * @param {AvControllerState_t} o_state [in]
300
+ * The current state of the AV controller.
301
+ *
302
+ * @param {Object} o_chg [in]
303
+ * An object containing only the properties that have changed since the last update.
304
+ * If a property is undefined here, no action is taken for that property.
305
+ *
306
+ * @param {Object} [o_src] [in]
307
+ * The object that originally triggered the update.
308
+ */
309
+ function HtmlMedia_handle_update(o_element, o_state, o_chg, o_src)
310
+ {
311
+ const b_chg_obj = sj_is_object(o_chg);
312
+
313
+ if (o_element && (o_state instanceof AvControllerState_t) && b_chg_obj)
314
+ {
315
+ const b_self_update = Object.is(o_element, o_src);
316
+
317
+ if ("paused" in o_chg)
318
+ {
319
+ if (o_state.paused)
320
+ {
321
+ o_element.pause();
322
+ }
323
+ else
324
+ {
325
+ o_element.play();
326
+ }
327
+ }
328
+
329
+ if ("playbackRate" in o_chg)
330
+ {
331
+ o_element.playbackRate = o_state.playbackRate;
332
+ }
333
+
334
+ if ((!b_self_update) && ("currentTime" in o_chg))
335
+ {
336
+ o_element.currentTime = o_state.currentTime;
337
+ }
338
+ }
339
+ }
340
+
341
+
342
+ /**
343
+ * Manually triggers the metadata loading logic if the media element is already
344
+ * initialized.
345
+ *
346
+ * This function checks the 'readyState' of the media element. If the metadata has
347
+ * already been loaded (i.e., readyState > HAVE_NOTHING), it manually invokes
348
+ * the 'HtmlMedia_loadedmetadata' handler to ensure state synchronization.
349
+ * This prevents initialization failures when the browser loads media before
350
+ * event listeners are attached.
351
+ *
352
+ * @param {HTMLMediaElement} o_element [in]
353
+ * The media element to check.
354
+ *
355
+ * @returns {number}
356
+ * 1 : Success. Metadata was already available, and the handler was executed.
357
+ * 0 : Pending. Metadata is not yet available; the system must wait for the event.
358
+ * -1 : Error. The provided element is invalid.
359
+ */
360
+ function HtmlMedia_loadedmetadata_manual(o_element)
361
+ {
362
+ let i_exe = - 1;
363
+
364
+ if (o_element)
365
+ {
366
+ if (HTMLMediaElement.HAVE_NOTHING < o_element.readyState)
367
+ {
368
+ HtmlMedia_loadedmetadata(o_element);
369
+ i_exe = 1;
370
+ }
371
+ else
372
+ {
373
+ i_exe = 0;
374
+ }
375
+ }
376
+
377
+ return i_exe;
378
+ }
379
+
380
+
381
+ /**
382
+ * Synchronizes the subject state and notifies observers when media metadata is loaded.
383
+ *
384
+ * This function acts as the core initialization logic for media-related state.
385
+ * It retrieves the current properties (e.g., duration, seekable ranges) from the
386
+ * media element to populate the Subject's data, and then triggers an initial
387
+ * update to synchronize all registered observers with this state.
388
+ *
389
+ * @param {HTMLMediaElement} o_element [in]
390
+ * The HTML media element (video or audio) that has loaded its metadata.
391
+ */
392
+ function HtmlMedia_loadedmetadata(o_element)
393
+ {
394
+ const s_key = sj_element_key_get(o_element);
395
+ const o_Subject = sj_Subject_get(s_key);
396
+
397
+ if (o_Subject instanceof Subject_t)
398
+ {
399
+ o_Subject.observer_retrieve();
400
+ o_Subject.observer_initial_update();
401
+ }
402
+ }
403
+
404
+
405
+ /**
406
+ * Retrieves the starting time (in seconds) of the first seekable range of a media
407
+ * element.
408
+ *
409
+ * @description
410
+ * This function checks the `seekable` property of the provided HTMLMediaElement.
411
+ * If at least one seekable range exists and contains a valid finite number,
412
+ * it returns the start time of the first range. Otherwise, it defaults to 0.
413
+ * This ensures a safe numeric fallback even if the media is in an unstable
414
+ * state or is a live stream where the start point has shifted.
415
+ *
416
+ * @param {HTMLMediaElement} o_element [in]
417
+ * The HTML media element (e.g., <video> or <audio>) to check.
418
+ *
419
+ * @returns {number}
420
+ * The start time of the first seekable range in seconds, or 0 if no range
421
+ * is available or the value is non-finite (Infinity/NaN).
422
+ */
423
+ function HtmlMedia_start_sec(o_element)
424
+ {
425
+ let d_start_sec;
426
+
427
+ if (0 < o_element.seekable.length)
428
+ {
429
+ d_start_sec = o_element.seekable.start(0);
430
+
431
+ const b_num = sj_is_number(d_start_sec);
432
+
433
+ if (!b_num)
434
+ {
435
+ d_start_sec = 0
436
+ }
437
+ }
438
+ else
439
+ {
440
+ d_start_sec = 0;
441
+ }
442
+
443
+ return d_start_sec;
444
+ }
445
+
446
+
447
+ /**
448
+ * Retrieves the ending time (in seconds) of the last seekable range of a media element.
449
+ *
450
+ * @description
451
+ * This function determines the effectively playable end point of the media.
452
+ * If seekable time ranges are available, it returns the end time of the very last range.
453
+ * If the duration is indefinite (Infinity), such as in live streams, it returns -1.
454
+ *
455
+ * @param {HTMLMediaElement} o_element [in]
456
+ * The HTML media element (e.g., <video> or <audio>) to inspect.
457
+ *
458
+ * @returns {number}
459
+ * The end time in seconds, or -1 if the duration is infinite/indefinite.
460
+ */
461
+ function HtmlMedia_end_sec(o_element)
462
+ {
463
+ let d_end_sec;
464
+
465
+ if (0 < o_element.seekable.length)
466
+ {
467
+ d_end_sec = o_element.seekable.end(o_element.seekable.length - 1);
468
+
469
+ const b_num = sj_is_number(d_end_sec);
470
+
471
+ if (!b_num)
472
+ {
473
+ d_end_sec = - 1.0;
474
+ }
475
+ }
476
+ else
477
+ {
478
+ d_end_sec = HtmlMedia_duration(o_element);
479
+ }
480
+
481
+ return d_end_sec;
482
+ }
483
+
484
+
485
+ /**
486
+ * Retrieves the duration of the media element in seconds.
487
+ *
488
+ * @description
489
+ * This function returns the total length of the media. If the duration is
490
+ * unavailable (NaN) or represents an infinite stream (Infinity), such as
491
+ * a live broadcast, it returns -1 to indicate an indefinite duration.
492
+ * This ensures the returned value is always a finite number, making it
493
+ * safe for serialization and consistent state management.
494
+ *
495
+ * @param {HTMLMediaElement} o_element [in]
496
+ * The HTML media element to inspect.
497
+ *
498
+ * @returns {number}
499
+ * The duration in seconds, or -1 if the duration is infinite or unavailable.
500
+ */
501
+ function HtmlMedia_duration(o_element)
502
+ {
503
+ let duration = o_element.duration;
504
+
505
+ const b_num = sj_is_number(duration);
506
+
507
+ if (!b_num)
508
+ {
509
+ duration = - 1.0;
510
+ }
511
+
512
+ return duration;
513
+ }
514
+
515
+