senza-sdk 4.3.1-e113d43.0 → 4.3.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.
@@ -1,7 +1,9 @@
1
- import { shaka } from "../interface/senzaShakaPlayer";
1
+ import { SenzaShakaPlayer as SenzaShakaInterface, shaka } from "../interface/senzaShakaPlayer";
2
2
 
3
3
  import { remotePlayer, lifecycle, getPlatformInfo } from "./api";
4
4
  import { sdkLogger, iso6393to1 } from "./utils";
5
+ import moment from "moment";
6
+
5
7
 
6
8
  // Define custom error category
7
9
  shaka.util.Error.Category.SENZA_PLAYER_ERROR = 50;
@@ -31,26 +33,7 @@ class SenzaError extends shaka.util.Error {
31
33
  }
32
34
 
33
35
 
34
- /**
35
- * SenzaShakaPlayer subclass of Shaka that handles both local and remote playback.
36
- *
37
- * @class SenzaShakaPlayer
38
- *
39
- * @example
40
- * import { SenzaShakaPlayer } from "./senzaShakaPlayer.js";
41
- *
42
- * try {
43
- * const videoElement = document.getElementById("video");
44
- * const player = new SenzaShakaPlayer(videoElement);
45
- * await player.load("http://playable.url/file.mpd");
46
- * await videoElement.play(); // will start the playback
47
- *
48
- * } catch (err) {
49
- * console.error("SenzaShakaPlayer failed with error", err);
50
- * }
51
- */
52
-
53
- export class SenzaShakaPlayer extends shaka.Player {
36
+ export class SenzaShakaPlayer extends SenzaShakaInterface {
54
37
  /** @private {SenzaShakaPlayer|null} Previous instance of the player */
55
38
  static _prevInstance = null;
56
39
 
@@ -72,9 +55,9 @@ export class SenzaShakaPlayer extends shaka.Player {
72
55
  * @private
73
56
  * @type {number}
74
57
  * @description Timeout in milliseconds to wait for playing event
75
- * @default 3000
58
+ * @default 4000
76
59
  */
77
- _playingTimeout = 3000;
60
+ _playingTimeout = 4000;
78
61
 
79
62
  /**
80
63
  * @private
@@ -89,7 +72,7 @@ export class SenzaShakaPlayer extends shaka.Player {
89
72
  * @description Whether to stop remote player on error
90
73
  * @default false
91
74
  */
92
- _shouldStopRemotePlayerOnError = false;
75
+ _shouldStopOnRemotePlayerError = false;
93
76
 
94
77
  /**
95
78
  * @private
@@ -135,7 +118,7 @@ export class SenzaShakaPlayer extends shaka.Player {
135
118
  });
136
119
  }
137
120
  },
138
- "pause" : () => {
121
+ "pause": () => {
139
122
  this._resetPlayPromise();
140
123
  this.remotePlayer.pause()
141
124
  .catch(error => {
@@ -280,13 +263,15 @@ export class SenzaShakaPlayer extends shaka.Player {
280
263
 
281
264
 
282
265
  /**
283
- * Handles errors for play promises and remote player events.
266
+ * Handles errors received by remote player while waiting for the playing event.
267
+ * The promise returned to the call for video element play will be rejected.
284
268
  * @private
285
269
  * @param {Error} error - The error object.
286
270
  */
287
271
  _handlePlayPromiseError(error) {
288
272
 
289
273
  sdkLogger.error("Error while waiting for playing event:", error);
274
+
290
275
  if (this._playPromiseReject) {
291
276
  this._playPromiseReject(error);
292
277
  this._playPromiseResolve = null;
@@ -296,16 +281,48 @@ export class SenzaShakaPlayer extends shaka.Player {
296
281
  clearTimeout(this._playTimeoutId);
297
282
  this._playTimeoutId = null;
298
283
  }
284
+
299
285
  }
300
286
 
301
287
  /**
302
- * Creates an instance of SenzaShakaPlayer, which is a subclass of shaka.Player.
303
- *
304
- * @param {HTMLVideoElement} videoElement - The video element to be used for local playback. This parameter is optional. If not provided, the video element can be attached later using the attach method.
305
- * @param {HTMLElement=} videoContainer - The videoContainer to construct UITextDisplayer
306
- * @param {function(shaka.Player)=} dependencyInjector Optional callback
307
- * which is called to inject mocks into the Player. Used for testing.
288
+ * @private
289
+ * @type {number}
290
+ * @description Minimum suggested presentation delay in seconds
291
+ * @default 15
308
292
  */
293
+ _minSuggestedPresentationDelay = 15;
294
+
295
+ /**
296
+ * Modifies the suggestedPresentationDelay in the manifest text
297
+ * @private
298
+ * @param {string} manifestText - The MPD manifest text
299
+ * @returns {string} - Modified manifest text , or undefined if no modification was done
300
+ */
301
+ _updateManifestDelayIfBelowMinimum(manifestText) {
302
+ // Look for suggestedPresentationDelay attribute
303
+ const match = manifestText.match(/suggestedPresentationDelay="([^"]+)"/);
304
+ if (match) {
305
+ const durationString = match[1];
306
+ const duration = moment.duration(durationString);
307
+ const currentDelay = duration.asSeconds();
308
+
309
+ sdkLogger.info(`Found suggestedPresentationDelay in manifest: ${currentDelay.toFixed(3)}s`);
310
+
311
+ if (currentDelay < this._minSuggestedPresentationDelay) {
312
+ // Replace the value in the manifest text with 3 decimal places
313
+ manifestText = manifestText.replace(
314
+ /suggestedPresentationDelay="[^"]+"/,
315
+ `suggestedPresentationDelay="PT${this._minSuggestedPresentationDelay.toFixed(3)}S"`
316
+ );
317
+ sdkLogger.info(`Updated manifest suggestedPresentationDelay to ${this._minSuggestedPresentationDelay.toFixed(3)}s`);
318
+ return manifestText;
319
+ }
320
+ } else {
321
+ sdkLogger.info("suggestedPresentationDelay is not defined at the manifest");
322
+ }
323
+ return undefined;
324
+ }
325
+
309
326
  constructor(videoElement, videoContainer, dependencyInjector) {
310
327
  super(videoElement, videoContainer, dependencyInjector);
311
328
 
@@ -323,7 +340,14 @@ export class SenzaShakaPlayer extends shaka.Player {
323
340
  this._addRemotePlayerEventListeners();
324
341
  SenzaShakaPlayer._prevInstance = this;
325
342
  const playTimeout = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"]?.playingEventTimeout;
326
- this._playingTimeout = (playTimeout >= 0) ? playTimeout*1000 : this._playingTimeout;
343
+ this._playingTimeout = (playTimeout >= 0) ? playTimeout * 1000 : this._playingTimeout;
344
+
345
+ // Initialize minSuggestedPresentationDelay from UI settings or use default
346
+ const uiSettings = getPlatformInfo()?.sessionInfo?.settings?.["ui-streamer"];
347
+ if (uiSettings?.minSuggestedPresentationDelay !== undefined) {
348
+ this._minSuggestedPresentationDelay = uiSettings.minSuggestedPresentationDelay;
349
+ sdkLogger.info(`Using configured minSuggestedPresentationDelay: ${this._minSuggestedPresentationDelay}s`);
350
+ }
327
351
 
328
352
  // if video element is provided, add the listeres here. In this case ,there is no need to call attach.
329
353
  if (videoElement) {
@@ -331,8 +355,25 @@ export class SenzaShakaPlayer extends shaka.Player {
331
355
  sdkLogger.warn("SenzaShakaPlayer constructor Adding videoElement in the constructor is going to be deprecated in the future. Please use attach method instead.");
332
356
  }
333
357
 
358
+ this.configure({
359
+ manifest: {
360
+ defaultPresentationDelay: this._minSuggestedPresentationDelay // in seconds
361
+ }
362
+ });
363
+
364
+ remotePlayer.configure({
365
+ minSuggestedPresentationDelay: this._minSuggestedPresentationDelay
366
+ });
367
+
368
+ this.addEventListener("buffering", () => {
369
+ if (this.videoElement) {
370
+ sdkLogger.info("Buffering at time:", this.videoElement.currentTime);
371
+ }
372
+ });
373
+
334
374
  }
335
375
 
376
+
336
377
  _attach(videoElement) {
337
378
  this.videoElement = videoElement;
338
379
 
@@ -379,27 +420,11 @@ export class SenzaShakaPlayer extends shaka.Player {
379
420
  this._attachVideoElementToRemotePlayer();
380
421
  }
381
422
 
382
- /**
383
- * Overrides the attach method of shaka.Player to attach the video element.
384
- *
385
- * @param {HTMLVideoElement} videoElement - The video element to be used for local playback.
386
- * @param {boolean} [initializeMediaSource=true] - Whether to initialize the media source.
387
- */
388
423
  async attach(videoElement, initializeMediaSource = true) {
389
424
  await super.attach(videoElement, initializeMediaSource);
390
425
  this._attach(videoElement);
391
426
  }
392
427
 
393
- /**
394
- * Detach the player from the current media element. Leaves the player in a
395
- * state where it cannot play media, until it has been attached to something
396
- * else.
397
- *
398
- * @param {boolean=} keepAdManager
399
- *
400
- * @return {!Promise}
401
- * @export
402
- */
403
428
  async detach(keepAdManager = false) {
404
429
  // Clear any pending timeout
405
430
  this._resetPlayPromise();
@@ -430,14 +455,6 @@ export class SenzaShakaPlayer extends shaka.Player {
430
455
  this.videoElement = null;
431
456
  }
432
457
 
433
- /**
434
- * Unloads the currently playing stream, if any.
435
- *
436
- * @param {boolean=} initializeMediaSource
437
- * @param {boolean=} keepAdManager
438
- * @return {!Promise}
439
- * @export
440
- */
441
458
  async unload(initializeMediaSource = true, keepAdManager = false) {
442
459
  // Call the remote player's unload method
443
460
  try {
@@ -452,27 +469,14 @@ export class SenzaShakaPlayer extends shaka.Player {
452
469
  await super.unload(initializeMediaSource, keepAdManager);
453
470
  }
454
471
 
455
- /**
456
- * Overrides the getTextTracks method to use the remote player's text tracks.
457
- * @returns {Array} An array of text tracks.
458
- */
459
472
  getTextLanguages() {
460
473
  return Object.keys(this._textTracksMap);
461
474
  }
462
475
 
463
- /**
464
- * Overrides the getAudioTracks method to use the remote player's audio tracks.
465
- * @returns {Array} An array of audio tracks.
466
- */
467
476
  getAudioLanguages() {
468
477
  return Object.keys(this._audioTracksMap);
469
478
  }
470
479
 
471
- /**
472
- * Overrides the selectAudioLanguage method to use the remote player's audio track selection.
473
- * @param {string} language - The language to select.
474
- * @param {string=} role - The role of the track to select. (e.g. 'main', 'caption', or 'commentary')
475
- */
476
480
  selectAudioLanguage(language, role) {
477
481
  sdkLogger.log("Selecting audio language:", language, "with role: ", role);
478
482
  if (this._audioTracksMap[language]) {
@@ -483,11 +487,6 @@ export class SenzaShakaPlayer extends shaka.Player {
483
487
  super.selectAudioLanguage(language, role);
484
488
  }
485
489
 
486
- /**
487
- * Overrides the selectTextLanguage method to use the remote player's text track selection.
488
- * @param {string} language - The language to select.
489
- * @param {string=} role - The role of the track to select.
490
- */
491
490
  selectTextLanguage(language, role) {
492
491
  sdkLogger.log("Selecting text language:", language, "with role:", role);
493
492
  if (this._textTracksMap[language]) {
@@ -498,20 +497,12 @@ export class SenzaShakaPlayer extends shaka.Player {
498
497
  super.selectTextLanguage(language, role);
499
498
  }
500
499
 
501
- /**
502
- * Overrides the setTextTrackVisibility method to use the remote player's text track visibility settings.
503
- * @param {boolean} visible - Whether the text tracks should be visible.
504
- */
505
500
  setTextTrackVisibility(visible) {
506
501
  sdkLogger.log("Setting text track visibility to:", visible);
507
502
  remotePlayer.setTextTrackVisibility(visible);
508
503
  super.setTextTrackVisibility(visible);
509
504
  }
510
505
 
511
- /**
512
- * Helper function that makes it easier to check if lifecycle.state is
513
- * either background or inTransitionToBackground.
514
- */
515
506
  get isInRemotePlayback() {
516
507
  return lifecycle.state === lifecycle.UiState.BACKGROUND || lifecycle.state === lifecycle.UiState.IN_TRANSITION_TO_BACKGROUND;
517
508
  }
@@ -556,11 +547,12 @@ export class SenzaShakaPlayer extends shaka.Player {
556
547
  const isCritical = this._isErrorCritical(remotePlayerErrorCode);
557
548
  const error = this._createSenzaError(remotePlayerErrorCode, message, isCritical);
558
549
  const errorMap = new Map();
550
+ const shouldStopPlayback = this._shouldStopOnRemotePlayerError && this.videoElement && isCritical;
559
551
  errorMap.set("detail", error);
560
552
 
561
553
  // Check if we should stop playback - only for critical errors when configured
562
- if (this._shouldStopRemotePlayerOnError &&
563
- this.videoElement && isCritical) {
554
+
555
+ if (shouldStopPlayback) {
564
556
  try {
565
557
  sdkLogger.warn("Stopping local player playback due to critical error");
566
558
  // Stop only local playback
@@ -568,18 +560,14 @@ export class SenzaShakaPlayer extends shaka.Player {
568
560
  } catch (stopError) {
569
561
  sdkLogger.error("Error while trying to stop video element playback:", stopError);
570
562
  }
563
+ // Handle error while waiting for play event. If the error is not critical or the system is configured not to stop remote player on error,
564
+ // the playback timeout will start the local playback
565
+ this._handlePlayPromiseError(error);
571
566
  }
572
- // Handle error while waiting for play event
573
- this._handlePlayPromiseError(error);
567
+
574
568
  this.dispatchEvent(new shaka.util.FakeEvent("error", errorMap));
575
569
  }
576
570
 
577
- /**
578
- * Loads a media URL into both local and remote players.
579
- *
580
- * @param {string} url - The URL of the media to load.
581
- * @returns {Promise<void>}
582
- */
583
571
  async load(url, startTime, mimeType) {
584
572
 
585
573
  // Create a promise that will resolve when _remotePlayerLoad is called
@@ -608,16 +596,30 @@ export class SenzaShakaPlayer extends shaka.Player {
608
596
 
609
597
  // This callbakc will be activated when the manifest is loaded. It will trigger load of the remote player.
610
598
  // This will ensure that the remote player is loaded only after the manifest is loaded by local player.
611
- const responseFilterCallback = async (type) => {
612
- if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST && !manifestLoadHandled) {
613
- manifestLoadHandled = true;
599
+ const responseFilterCallback = async (type, response) => {
600
+ if (type === shaka.net.NetworkingEngine.RequestType.MANIFEST) {
614
601
  try {
615
- await this._remotePlayerLoad(url, startTime);
616
- remoteLoadResolver();
602
+ if (response.data && this._minSuggestedPresentationDelay > 0) {
603
+ const manifestText = new TextDecoder().decode(response.data);
604
+ const modifiedText = this._updateManifestDelayIfBelowMinimum(manifestText);
605
+ if (modifiedText) {
606
+ const responseData = new TextEncoder().encode(modifiedText).buffer;
607
+ response.data = responseData;
608
+ }
609
+ }
617
610
  } catch (error) {
618
- remoteLoadRejecter(error);
611
+ sdkLogger.error("Error processing manifest:", error);
619
612
  }
620
613
 
614
+ if (!manifestLoadHandled) {
615
+ manifestLoadHandled = true;
616
+ try {
617
+ await this._remotePlayerLoad(url, startTime);
618
+ remoteLoadResolver();
619
+ } catch (error) {
620
+ remoteLoadRejecter(error);
621
+ }
622
+ }
621
623
  }
622
624
  };
623
625
 
@@ -652,13 +654,6 @@ export class SenzaShakaPlayer extends shaka.Player {
652
654
 
653
655
  }
654
656
 
655
- /***
656
- *
657
- * Configure the Player instance.
658
- * @param {Object} config the configuration object
659
- * @returns {Boolean}
660
- */
661
-
662
657
  async destroy() {
663
658
  await lifecycle.moveToForeground();
664
659
  SenzaShakaPlayer._prevInstance = null;
@@ -666,10 +661,6 @@ export class SenzaShakaPlayer extends shaka.Player {
666
661
  return super.destroy();
667
662
  }
668
663
 
669
- /**
670
- * A temporary override for older versions of Shaka.
671
- * Senza doesn't support out-of-band subtitles
672
- */
673
664
  addTextTrack(uri, language, kind, mimeType, codec, label, forced = false) {
674
665
  sdkLogger.warn("addTextTrack is deprecated, please use addTextTrackAsync");
675
666
  super.addTextTrackAsync(uri, language, kind, mimeType, codec, label, forced).then(subs => {
@@ -677,30 +668,16 @@ export class SenzaShakaPlayer extends shaka.Player {
677
668
  });
678
669
  }
679
670
 
680
- /**
681
- * Override the configure method to add custom configuration handling
682
- * Supports the following additional configuration options:
683
- * - shouldStopRemotePlayerOnError: boolean - If true, remote player will be stopped on error
684
- *
685
- * @override
686
- * @param {Object} config - Configuration object to be merged with existing config
687
- * @param {boolean} [config.shouldStopRemotePlayerOnError=true] - Whether to stop remote player on error
688
- * @example
689
- * player.configure({
690
- * shouldStopRemotePlayerOnError: false, // Don't stop remote player on error
691
- * // ... other shaka configurations
692
- * });
693
- */
694
671
  configure(config) {
695
672
  sdkLogger.log("configure player with: ", JSON.stringify(config));
696
673
 
697
674
  // Handle custom configuration
698
- if (config.shouldStopRemotePlayerOnError !== undefined) {
699
- this._shouldStopRemotePlayerOnError = !!config.shouldStopRemotePlayerOnError;
675
+ if (config.shouldStopOnRemotePlayerError !== undefined) {
676
+ this._shouldStopOnRemotePlayerError = !!config.shouldStopOnRemotePlayerError;
700
677
  // Remove our custom config so it doesn't get passed to parent
701
678
  // Use rest operator without creating a named variable for the removed property
702
679
  // eslint-disable-next-line no-unused-vars
703
- const { shouldStopRemotePlayerOnError, ...shakaConfig } = config;
680
+ const { shouldStopOnRemotePlayerError, ...shakaConfig } = config;
704
681
  config = shakaConfig;
705
682
  }
706
683
 
@@ -1,30 +1,27 @@
1
1
  import { noop } from "./utils";
2
+
3
+ /**
4
+ * alarm event
5
+ *
6
+ * @event AlarmManager#MyAlarm
7
+ * @description Fired when time of 'MyAlarm' arrives. If this alarm triggers the application load and the application doesn't call
8
+ * lifecycle.moveToForeground() in the alarm callback (i.e. the application remains in the background after the callback is completed),
9
+ * the application will be unloaded.
10
+ * NOTE: If you perform async operations in the callback (without moving to foreground), you must wait for the async
11
+ * operation to finish before returning from the callback, otherwise the application will be unloaded before the async operation is finished.<br>
12
+ * @example
13
+ * alarmManager.addEventListener("MyAlarm", async (e) => {
14
+ * console.log("alarm MyAlarm arrived with data", e.detail);
15
+ * await fetch("http://www.example.com");
16
+ * });
17
+ * alarmManager.addAlarm("MyAlarm", Date.now() + 60*60*1000, "MyData");
18
+ */
19
+
2
20
  /**
3
- * @class AlarmManager
4
21
  * AlarmManager is a singleton class that manages the alarms in the application. It fires events whose types are the names of the alarms.
5
22
  * @fires MyAlarm
6
23
  */
7
- export class AlarmManager extends EventTarget {
8
-
9
- /**
10
- * alarm event
11
- *
12
- * @event AlarmManager#MyAlarm
13
- * @description Fired when time of 'MyAlarm' arrives. If this alarm triggers the application load and the application doesn't call
14
- * lifecycle.moveToForeground() in the alarm callback (i.e. the application remains in the background after the callback is completed),
15
- * the application will be unloaded.
16
- * NOTE: If you perform async operations in the callback (without moving to foreground), you must wait for the async
17
- * operation to finish before returning from the callback, otherwise the application will be unloaded before the async operation is finished.<br>
18
- * @example
19
- * alarmManager.addEventListener("MyAlarm", async (e) => {
20
- * console.log("alarm MyAlarm arrived with data", e.detail);
21
- * await fetch("http://www.example.com");
22
- * });
23
- * alarmManager.addAlarm("MyAlarm", Date.now() + 60*60*1000, "MyData");
24
- */
25
- constructor() {
26
- super();
27
- }
24
+ class AlarmManager extends EventTarget {
28
25
 
29
26
  addEventListener(type, callback) {
30
27
  noop("AlarmManager.addEventListener", type, callback);
@@ -67,3 +64,13 @@ export class AlarmManager extends EventTarget {
67
64
  return noop("AlarmManager.getActiveAlarms");
68
65
  }
69
66
  }
67
+
68
+ /**
69
+ * @module
70
+ * @type {AlarmManager}
71
+ * @example
72
+ * import { alarmManager } from "senza-sdk";
73
+ *
74
+ * @return {AlarmManager} pointer to the AlarmManager singleton
75
+ */
76
+ export { AlarmManager };
@@ -204,6 +204,7 @@ const setupSequence = (components, items) => {
204
204
  };
205
205
  methodInject(components, inject, (name) => name.startsWith("_") || name.startsWith("get"));
206
206
  handleKeys(items);
207
+ playersEvents(components, items);
207
208
  sdkLogger.log("Sequence initialized.");
208
209
  };
209
210
 
@@ -257,3 +258,37 @@ export const showSequence = (visible = true) => {
257
258
  }
258
259
  container.style.visibility = visible ? "visible" : "hidden";
259
260
  };
261
+ function playersEvents(components, items) {
262
+ components.remotePlayer.addEventListener("videoelementattached", () => {
263
+ const video = components.remotePlayer._videoElement;
264
+ if (video && !video._devSequenceRateChangeHandler) {
265
+ video._devSequenceRateChangeHandler = () => {
266
+ items.push({
267
+ component: "localPlayer",
268
+ id: "ratechange=" + video.playbackRate,
269
+ time: performance.now() - currentTime
270
+ });
271
+ };
272
+ video.addEventListener("ratechange", video._devSequenceRateChangeHandler);
273
+ }
274
+
275
+ if (video && !video._devSequencePlayingHandler) {
276
+ video._devSequencePlayingHandler = () => {
277
+ items.push({
278
+ component: "localPlayer",
279
+ id: "playing event",
280
+ time: performance.now() - currentTime
281
+ });
282
+ };
283
+ video.addEventListener("playing", video._devSequencePlayingHandler);
284
+ }
285
+ });
286
+ components.remotePlayer.addEventListener("playing", () => {
287
+ items.push({
288
+ component: "remotePlayer",
289
+ id: "playing event",
290
+ time: performance.now() - currentTime
291
+ });
292
+ });
293
+ }
294
+
@@ -1,30 +1,25 @@
1
1
  import { sdkLogger, noop } from "./utils.js";
2
+ /**
3
+ * @event DeviceManager#wifiInfoUpdated
4
+ * @deprecated Instead, call deviceManager.getWifiInfo() periodically
5
+ * @example
6
+ * deviceManager.addEventListener("wifiInfoUpdated", () => {
7
+ * console.info("Wifi info has been updated to", deviceManager.wifiInfo);
8
+ * });
9
+ *
10
+ */
2
11
 
3
- const wifiInfo = {};
12
+ const wifiInfo = {
13
+ level: 0,
14
+ quality: 0,
15
+ ssid: "unknown",
16
+ bssid: "unknown"
17
+ };
4
18
 
5
19
  /**
6
- * @class DeviceManager
7
20
  * DeviceManager is a singleton class that manages the device
8
21
  */
9
22
  export class DeviceManager extends EventTarget {
10
-
11
- constructor() {
12
- super();
13
- wifiInfo.level = 0;
14
- wifiInfo.quality = 0;
15
- wifiInfo.ssid = "unknown";
16
- wifiInfo.bssid = "unknown";
17
- /**
18
- * @deprecated Instead, call deviceManager.getWifiInfo() periodically
19
- * @event DeviceManager#wifiInfoUpdated
20
- * @example
21
- * deviceManager.addEventListener("wifiInfoUpdated", () => {
22
- * console.info("Wifi info has been updated to", deviceManager.wifiInfo);
23
- * });
24
- *
25
- */
26
- }
27
-
28
23
  /**
29
24
  * @property {object} DeviceInfo
30
25
  * @property {string} DeviceInfo.deviceId
@@ -131,3 +126,18 @@ export class DeviceManager extends EventTarget {
131
126
  return Promise.resolve({});
132
127
  }
133
128
  }
129
+
130
+
131
+ /**
132
+ * @module
133
+ * @type {DeviceManager}
134
+ * @example
135
+ * import { deviceManager } from "senza-sdk";
136
+ * const wifiInfo = await deviceManager.getWifiInfo();
137
+ * console.info(wifiInfo.ssid);
138
+ * await deviceManager.clearWifi();
139
+ * deviceManager.reboot();
140
+ *
141
+ * @return {DeviceManager} pointer to the DeviceManager singleton
142
+ */
143
+ "needed for the module doc comment to be recognized";