bitmovin-player-react-native 0.3.1 → 0.5.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.
Files changed (56) hide show
  1. package/README.md +486 -24
  2. package/RNBitmovinPlayer.podspec +5 -1
  3. package/android/build.gradle +8 -5
  4. package/android/src/main/java/com/bitmovin/player/reactnative/AnalyticsModule.kt +154 -0
  5. package/android/src/main/java/com/bitmovin/player/reactnative/DrmModule.kt +4 -5
  6. package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +41 -5
  7. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +318 -2
  8. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +70 -9
  9. package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +3 -0
  10. package/android/src/main/java/com/bitmovin/player/reactnative/SourceModule.kt +4 -5
  11. package/android/src/main/java/com/bitmovin/player/reactnative/UuidModule.kt +3 -1
  12. package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +389 -0
  13. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/Any.kt +27 -0
  14. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt +8 -0
  15. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReadableArray.kt +19 -2
  16. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/String.kt +8 -0
  17. package/android/src/main/java/com/bitmovin/player/reactnative/extensions/WritableMap.kt +19 -0
  18. package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerBridge.kt +37 -0
  19. package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerModule.kt +73 -0
  20. package/android/src/main/java/com/bitmovin/player/reactnative/ui/RNPictureInPictureHandler.kt +191 -0
  21. package/ios/AnalyticsModule.m +14 -0
  22. package/ios/AnalyticsModule.swift +180 -0
  23. package/ios/AudioSessionModule.m +10 -0
  24. package/ios/AudioSessionModule.swift +65 -0
  25. package/ios/Event+JSON.swift +134 -0
  26. package/ios/FullscreenHandlerBridge.swift +33 -0
  27. package/ios/FullscreenHandlerModule.m +9 -0
  28. package/ios/FullscreenHandlerModule.swift +71 -0
  29. package/ios/PlayerModule.m +6 -0
  30. package/ios/PlayerModule.swift +44 -0
  31. package/ios/RCTConvert+BitmovinPlayer.swift +382 -0
  32. package/ios/RNPlayerView+PlayerListener.swift +56 -0
  33. package/ios/RNPlayerView+UserInterfaceListener.swift +35 -0
  34. package/ios/RNPlayerView.swift +22 -0
  35. package/ios/RNPlayerViewManager.m +24 -1
  36. package/ios/RNPlayerViewManager.swift +23 -1
  37. package/lib/index.d.ts +1013 -150
  38. package/lib/index.js +251 -48
  39. package/lib/index.mjs +231 -34
  40. package/package.json +1 -1
  41. package/src/advertising.ts +155 -0
  42. package/src/analytics/collector.ts +97 -0
  43. package/src/analytics/config.ts +218 -0
  44. package/src/analytics/index.ts +2 -0
  45. package/src/audioSession.ts +47 -0
  46. package/src/components/PlayerView/events.ts +49 -3
  47. package/src/components/PlayerView/index.tsx +68 -11
  48. package/src/components/PlayerView/native.ts +4 -1
  49. package/src/events.ts +255 -0
  50. package/src/index.ts +4 -0
  51. package/src/media.ts +33 -0
  52. package/src/player.ts +57 -1
  53. package/src/source.ts +4 -0
  54. package/src/styleConfig.ts +87 -0
  55. package/src/ui/fullscreenhandler.ts +19 -0
  56. package/src/ui/fullscreenhandlerbridge.ts +59 -0
package/src/events.ts CHANGED
@@ -1,4 +1,13 @@
1
+ import {
2
+ Ad,
3
+ AdBreak,
4
+ AdConfig,
5
+ AdItem,
6
+ AdQuartile,
7
+ AdSourceType,
8
+ } from './advertising';
1
9
  import { SubtitleTrack } from './subtitleTrack';
10
+ import { VideoQuality } from './media';
2
11
 
3
12
  /**
4
13
  * Base event type for all events.
@@ -158,6 +167,16 @@ export interface SeekEvent extends Event {
158
167
  */
159
168
  export interface SeekedEvent extends Event {}
160
169
 
170
+ /**
171
+ * Emitted when the player begins to stall and to buffer due to an empty buffer.
172
+ */
173
+ export interface StallStartedEvent extends Event {}
174
+
175
+ /**
176
+ * Emitted when the player ends stalling, due to enough data in the buffer.
177
+ */
178
+ export interface StallEndedEvent extends Event {}
179
+
161
180
  /**
162
181
  * Emitted when the current playback time has changed.
163
182
  */
@@ -243,3 +262,239 @@ export interface SubtitleChangedEvent extends Event {
243
262
  */
244
263
  newSubtitleTrack: SubtitleTrack;
245
264
  }
265
+
266
+ /**
267
+ * Emitted when the player enters Picture in Picture mode.
268
+ *
269
+ * @platform iOS, Android
270
+ */
271
+ export interface PictureInPictureEnterEvent extends Event {}
272
+
273
+ /**
274
+ * Emitted when the player exits Picture in Picture mode.
275
+ *
276
+ * @platform iOS, Android
277
+ */
278
+ export interface PictureInPictureExitEvent extends Event {}
279
+
280
+ /**
281
+ * Emitted when the player has finished entering Picture in Picture mode on iOS.
282
+ *
283
+ * @platform iOS
284
+ */
285
+ export interface PictureInPictureEnteredEvent extends Event {}
286
+
287
+ /**
288
+ * Emitted when the player has finished exiting Picture in Picture mode on iOS.
289
+ *
290
+ * @platform iOS
291
+ */
292
+ export interface PictureInPictureExitedEvent extends Event {}
293
+
294
+ /**
295
+ * Emitted when the fullscreen functionality has been enabled.
296
+ *
297
+ * @platform iOS, Android
298
+ */
299
+ export interface FullscreenEnabledEvent extends Event {}
300
+
301
+ /**
302
+ * Emitted when the fullscreen functionality has been disabled.
303
+ *
304
+ * @platform iOS, Android
305
+ */
306
+ export interface FullscreenDisabledEvent extends Event {}
307
+
308
+ /**
309
+ * Emitted when the player enters fullscreen mode.
310
+ *
311
+ * @platform iOS, Android
312
+ */
313
+ export interface FullscreenEnterEvent extends Event {}
314
+
315
+ /**
316
+ * Emitted when the player exits fullscreen mode.
317
+ *
318
+ * @platform iOS, Android
319
+ */
320
+ export interface FullscreenExitEvent extends Event {}
321
+
322
+ /**
323
+ * Emitted when the availability of the Picture in Picture mode changed on Android.
324
+ *
325
+ * @platform Android
326
+ */
327
+ export interface PictureInPictureAvailabilityChangedEvent extends Event {
328
+ /**
329
+ * Whether Picture in Picture is available.
330
+ */
331
+ isPictureInPictureAvailable: boolean;
332
+ }
333
+
334
+ /**
335
+ * Emitted when an ad break has started.
336
+ */
337
+ export interface AdBreakStartedEvent extends Event {
338
+ /**
339
+ * The `AdBreak` that has started.
340
+ */
341
+ adBreak?: AdBreak;
342
+ }
343
+
344
+ /**
345
+ * Emitted when an ad break has finished.
346
+ */
347
+ export interface AdBreakFinishedEvent extends Event {
348
+ /**
349
+ * The `AdBreak` that has finished.
350
+ */
351
+ adBreak?: AdBreak;
352
+ }
353
+
354
+ /**
355
+ * Emitted when the playback of an ad has started.
356
+ */
357
+ export interface AdStartedEvent extends Event {
358
+ /**
359
+ * The `Ad` this event is related to.
360
+ */
361
+ ad?: Ad;
362
+ /**
363
+ * The target URL to open once the user clicks on the ad.
364
+ */
365
+ clickThroughUrl?: string;
366
+ /**
367
+ * The `AdSourceType` of the started `Ad`.
368
+ */
369
+ clientType?: AdSourceType;
370
+ /**
371
+ * The duration of the ad in seconds.
372
+ */
373
+ duration: number;
374
+ /**
375
+ * The index of the ad in the queue.
376
+ */
377
+ indexInQueue: number;
378
+ /**
379
+ * The position of the corresponding `Ad`.
380
+ */
381
+ position?: string;
382
+ /**
383
+ * The skip offset of the ad in seconds.
384
+ */
385
+ skipOffset: number;
386
+ /**
387
+ * The content time at which the `Ad` is played.
388
+ */
389
+ timeOffset: number;
390
+ }
391
+
392
+ /**
393
+ * Emitted when an ad has finished playback.
394
+ */
395
+ export interface AdFinishedEvent extends Event {
396
+ /**
397
+ * The `Ad` that finished playback.
398
+ */
399
+ ad?: Ad;
400
+ }
401
+
402
+ /**
403
+ * Emitted when an error with the ad playback occurs.
404
+ */
405
+ export interface AdErrorEvent extends ErrorEvent {
406
+ /**
407
+ * The `AdConfig` for which the ad error occurred.
408
+ */
409
+ adConfig?: AdConfig;
410
+ /**
411
+ * The `AdItem` for which the ad error occurred.
412
+ */
413
+ adItem?: AdItem;
414
+ }
415
+
416
+ /**
417
+ * Emitted when an ad was clicked.
418
+ */
419
+ export interface AdClickedEvent extends Event {
420
+ /**
421
+ * The click through url of the ad.
422
+ */
423
+ clickThroughUrl?: string;
424
+ }
425
+
426
+ /**
427
+ * Emitted when an ad was skipped.
428
+ */
429
+ export interface AdSkippedEvent extends Event {
430
+ /**
431
+ * The `Ad` that was skipped.
432
+ */
433
+ ad?: Ad;
434
+ }
435
+
436
+ /**
437
+ * Emitted when the playback of an ad has progressed over a quartile boundary.
438
+ */
439
+ export interface AdQuartileEvent extends Event {
440
+ /**
441
+ * The `AdQuartile` boundary that playback has progressed over.
442
+ */
443
+ quartile: AdQuartile;
444
+ }
445
+
446
+ /**
447
+ * Emitted when an ad manifest was successfully downloaded, parsed and added into the ad break schedule.
448
+ */
449
+ export interface AdScheduledEvent extends Event {
450
+ /**
451
+ * The total number of scheduled ads.
452
+ */
453
+ numberOfAds: number;
454
+ }
455
+
456
+ /**
457
+ * Emitted when the download of an ad manifest is started.
458
+ */
459
+ export interface AdManifestLoadEvent extends Event {
460
+ /**
461
+ * The `AdBreak` this event is related to.
462
+ */
463
+ adBreak?: AdBreak;
464
+ /**
465
+ * The `AdConfig` of the loaded ad manifest.
466
+ */
467
+ adConfig?: AdConfig;
468
+ }
469
+
470
+ /**
471
+ * Emitted when an ad manifest was successfully loaded.
472
+ */
473
+ export interface AdManifestLoadedEvent extends Event {
474
+ /**
475
+ * The `AdBreak` this event is related to.
476
+ */
477
+ adBreak?: AdBreak;
478
+ /**
479
+ * The `AdConfig` of the loaded ad manifest.
480
+ */
481
+ adConfig?: AdConfig;
482
+ /**
483
+ * How long it took for the ad tag to be downloaded in milliseconds.
484
+ */
485
+ downloadTime: number;
486
+ }
487
+
488
+ /**
489
+ * Emitted when the current video playback quality has changed.
490
+ */
491
+ export interface VideoPlaybackQualityChangedEvent extends Event {
492
+ /**
493
+ * The new quality
494
+ */
495
+ newVideoQuality: VideoQuality;
496
+ /**
497
+ * The previous quality
498
+ */
499
+ oldVideoQuality: VideoQuality;
500
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,6 @@
1
+ export * from './advertising';
2
+ export * from './analytics';
3
+ export * from './audioSession';
1
4
  export * from './components';
2
5
  export * from './drm';
3
6
  export * from './events';
@@ -5,3 +8,4 @@ export * from './hooks';
5
8
  export * from './player';
6
9
  export * from './source';
7
10
  export * from './subtitleTrack';
11
+ export * from './styleConfig';
package/src/media.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Quality definition of a video representation.
3
+ */
4
+ export interface VideoQuality {
5
+ /**
6
+ * The id of the media quality.
7
+ */
8
+ id: string;
9
+ /**
10
+ * The label of the media quality that should be exposed to the user.
11
+ */
12
+ label?: string;
13
+ /**
14
+ * The bitrate of the media quality.
15
+ */
16
+ bitrate?: number;
17
+ /**
18
+ * The codec of the media quality.
19
+ */
20
+ codec?: string;
21
+ /**
22
+ * The frame rate of the video quality. If the frame rate is not known or not applicable a value of -1 will be returned.
23
+ */
24
+ frameRate?: number;
25
+ /**
26
+ * The height of the video quality.
27
+ */
28
+ height?: number;
29
+ /**
30
+ * The width of the video quality.
31
+ */
32
+ width?: number;
33
+ }
package/src/player.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { NativeModules, Platform } from 'react-native';
2
+ import { AdItem, AdvertisingConfig } from './advertising';
3
+ import { AnalyticsCollector, AnalyticsConfig } from './analytics';
2
4
  import NativeInstance, { NativeInstanceConfig } from './nativeInstance';
3
5
  import { Source, SourceConfig } from './source';
4
6
  import { SubtitleTrack } from './subtitleTrack';
7
+ import { StyleConfig } from './styleConfig';
5
8
  import { TweaksConfig } from './tweaksConfig';
6
9
 
7
10
  const PlayerModule = NativeModules.PlayerModule;
@@ -33,10 +36,22 @@ export interface PlayerConfig extends NativeInstanceConfig {
33
36
  * Configures playback behaviour. A default PlaybackConfig is set initially.
34
37
  */
35
38
  playbackConfig?: PlaybackConfig;
39
+ /**
40
+ * Configures the visual presentation and behaviour of the player UI. A default StyleConfig is set initially.
41
+ */
42
+ styleConfig?: StyleConfig;
43
+ /**
44
+ * Configures advertising functionality. A default AdvertisingConfig is set initially.
45
+ */
46
+ advertisingConfig?: AdvertisingConfig;
36
47
  /**
37
48
  * Configures experimental features. A default TweaksConfig is set initially.
38
49
  */
39
50
  tweaksConfig?: TweaksConfig;
51
+ /**
52
+ * Configures analytics functionality.
53
+ */
54
+ analyticsConfig?: AnalyticsConfig;
40
55
  }
41
56
 
42
57
  /**
@@ -103,7 +118,7 @@ export interface PlaybackConfig {
103
118
  */
104
119
  isBackgroundPlaybackEnabled?: boolean;
105
120
  /**
106
- * Whether the picture-in-picture mode option is enabled for iOS or not. Default is `false`.
121
+ * Whether the Picture in Picture mode option is enabled or not. Default is `false`.
107
122
  * @example
108
123
  * ```
109
124
  * const player = new Player({
@@ -130,6 +145,10 @@ export class Player extends NativeInstance<PlayerConfig> {
130
145
  * Currently active source, or `null` if none is active.
131
146
  */
132
147
  source?: Source;
148
+ /**
149
+ * Analytics collector currently attached to this player instance.
150
+ */
151
+ analyticsCollector?: AnalyticsCollector;
133
152
  /**
134
153
  * Whether the native `Player` object has been created.
135
154
  */
@@ -145,6 +164,12 @@ export class Player extends NativeInstance<PlayerConfig> {
145
164
  initialize = () => {
146
165
  if (!this.isInitialized) {
147
166
  PlayerModule.initWithConfig(this.nativeId, this.config);
167
+ const analyticsConfig = this.config?.analyticsConfig;
168
+ if (analyticsConfig) {
169
+ this.analyticsCollector = new AnalyticsCollector(analyticsConfig);
170
+ this.analyticsCollector?.initialize();
171
+ this.analyticsCollector?.attach(this.nativeId);
172
+ }
148
173
  this.isInitialized = true;
149
174
  }
150
175
  };
@@ -156,6 +181,7 @@ export class Player extends NativeInstance<PlayerConfig> {
156
181
  if (!this.isDestroyed) {
157
182
  PlayerModule.destroy(this.nativeId);
158
183
  this.source?.destroy();
184
+ this.analyticsCollector?.destroy();
159
185
  this.isDestroyed = true;
160
186
  }
161
187
  };
@@ -321,4 +347,34 @@ export class Player extends NativeInstance<PlayerConfig> {
321
347
  getAvailableSubtitles = async (): Promise<SubtitleTrack[]> => {
322
348
  return PlayerModule.getAvailableSubtitles(this.nativeId);
323
349
  };
350
+
351
+ /**
352
+ * Dynamically schedules the `adItem` for playback.
353
+ * Has no effect if there is no active playback session.
354
+ *
355
+ * @param adItem - Ad to be scheduled for playback.
356
+ *
357
+ * @platform iOS, Android
358
+ */
359
+ scheduleAd = (adItem: AdItem) => {
360
+ PlayerModule.scheduleAd(this.nativeId, adItem);
361
+ };
362
+
363
+ /**
364
+ * Skips the current ad.
365
+ * Has no effect if the current ad is not skippable or if no ad is being played back.
366
+ *
367
+ * @platform iOS, Android
368
+ */
369
+ skipAd = () => {
370
+ PlayerModule.skipAd(this.nativeId);
371
+ };
372
+
373
+ /**
374
+ * @returns `true` while an ad is being played back or when main content playback has been paused for ad playback.
375
+ * @platform iOS, Android
376
+ */
377
+ isAd = (): Promise<boolean> => {
378
+ return PlayerModule.isAd(this.nativeId);
379
+ };
324
380
  }
package/src/source.ts CHANGED
@@ -78,6 +78,10 @@ export interface SourceConfig extends NativeInstanceConfig {
78
78
  * External subtitle tracks to be added into the player.
79
79
  */
80
80
  subtitleTracks?: SideLoadedSubtitleTrack[];
81
+ /**
82
+ * External thumbnails to be added into the player.
83
+ */
84
+ thumbnailTrack?: string;
81
85
  }
82
86
 
83
87
  /**
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Contains config values which can be used to alter the visual presentation and behaviour of the player UI.
3
+ */
4
+ export interface StyleConfig {
5
+ /**
6
+ * Sets if the UI should be enabled or not. Default value is true.
7
+ * @example
8
+ * ```
9
+ * const player = new Player({
10
+ * styleConfig: {
11
+ * isUiEnabled: false,
12
+ * },
13
+ * });
14
+ * ```
15
+ */
16
+ isUiEnabled?: boolean;
17
+ /**
18
+ * Set the CSS file that will be used for the UI. The default CSS file will be completely replaced by the CSS file set with this property.
19
+ * @example
20
+ * ```
21
+ * const player = new Player({
22
+ * styleConfig: {
23
+ * playerUiCss: 'https://domain.tld/path/to/bitmovinplayer-ui.css',
24
+ * },
25
+ * });
26
+ * ```
27
+ * @platform iOS, Android
28
+ */
29
+ playerUiCss?: string;
30
+ /**
31
+ * Set a CSS file which contains supplemental styles for the player UI. These styles will be added to the default CSS file or the CSS file set with StyleConfig#playerUiCss.
32
+ * @example
33
+ * ```
34
+ * const player = new Player({
35
+ * styleConfig: {
36
+ * supplementalPlayerUiCss: 'https://domain.tld/path/to/bitmovinplayer-supplemental-ui.css',
37
+ * },
38
+ * });
39
+ * ```
40
+ * @platform iOS, Android
41
+ */
42
+ supplementalPlayerUiCss?: string;
43
+ /**
44
+ * Sets the JS file that will be used for the UI. The default JS file will be completely replaced by the JS file set with this property.
45
+ * @example
46
+ * ```
47
+ * const player = new Player({
48
+ * styleConfig: {
49
+ * playerUiJs: 'https://domain.tld/path/to/bitmovinplayer-ui.js',
50
+ * },
51
+ * });
52
+ * ```
53
+ * @platform iOS, Android
54
+ */
55
+ playerUiJs?: string;
56
+ /**
57
+ * Determines how the video content is scaled or stretched within the parent container’s bounds. Possible values are defined in ScalingMode.
58
+ * Default value is ScalingMode.fit.
59
+ * @example
60
+ * ```
61
+ * const player = new Player({
62
+ * styleConfig: {
63
+ * scalingMode: ScalingMode.Zoom,
64
+ * },
65
+ * });
66
+ * ```
67
+ */
68
+ scalingMode?: ScalingMode;
69
+ }
70
+
71
+ /**
72
+ * Specifies how the video content is scaled or stretched.
73
+ */
74
+ export enum ScalingMode {
75
+ /**
76
+ * Specifies that the player should preserve the video’s aspect ratio and fit the video within the container's bounds.
77
+ */
78
+ Fit = 'Fit',
79
+ /**
80
+ * Specifies that the video should be stretched to fill the container’s bounds. The aspect ratio may not be preserved.
81
+ */
82
+ Stretch = 'Stretch',
83
+ /**
84
+ * Specifies that the player should preserve the video’s aspect ratio and fill the container’s bounds.
85
+ */
86
+ Zoom = 'Zoom',
87
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Handles the UI state change when fullscreen should be entered or exited.
3
+ */
4
+ export interface FullscreenHandler {
5
+ /**
6
+ * Indicates if the UI is currently in fullscreen mode
7
+ */
8
+ isFullscreenActive: boolean;
9
+
10
+ /**
11
+ * Is called by the `PlayerView` when the UI should enter fullscreen mode.
12
+ */
13
+ enterFullscreen(): void;
14
+
15
+ /**
16
+ * Is called by the `PlayerView` when the UI should exit fullscreen mode.
17
+ */
18
+ exitFullscreen(): void;
19
+ }
@@ -0,0 +1,59 @@
1
+ import { NativeModules } from 'react-native';
2
+ import BatchedBridge from 'react-native/Libraries/BatchedBridge/BatchedBridge';
3
+ import { FullscreenHandler } from './fullscreenhandler';
4
+
5
+ const Uuid = NativeModules.UuidModule;
6
+ const FullscreenHandlerModule = NativeModules.FullscreenHandlerModule;
7
+
8
+ /**
9
+ * Takes care of JS/Native communication for a FullscreenHandler.
10
+ */
11
+ export class FullscreenHandlerBridge {
12
+ readonly nativeId: string;
13
+ fullscreenHandler?: FullscreenHandler;
14
+ isDestroyed: boolean;
15
+
16
+ constructor(nativeId?: string) {
17
+ this.nativeId = nativeId ?? Uuid.generate();
18
+ this.isDestroyed = false;
19
+ BatchedBridge.registerCallableModule(
20
+ `FullscreenBridge-${this.nativeId}`,
21
+ this
22
+ );
23
+ FullscreenHandlerModule.registerHandler(this.nativeId);
24
+ }
25
+
26
+ /**
27
+ * Destroys the native FullscreenHandler
28
+ */
29
+ destroy() {
30
+ if (!this.isDestroyed) {
31
+ FullscreenHandlerModule.destroy(this.nativeId);
32
+ this.isDestroyed = true;
33
+ }
34
+ }
35
+
36
+ // noinspection JSUnusedGlobalSymbols
37
+ /**
38
+ * Called by native code, when the UI should enter fullscreen.
39
+ */
40
+ enterFullscreen(): void {
41
+ this.fullscreenHandler?.enterFullscreen();
42
+ FullscreenHandlerModule.onFullscreenChanged(
43
+ this.nativeId,
44
+ this.fullscreenHandler?.isFullscreenActive ?? false
45
+ );
46
+ }
47
+
48
+ // noinspection JSUnusedGlobalSymbols
49
+ /**
50
+ * Called by native code, when the UI should exit fullscreen.
51
+ */
52
+ exitFullscreen(): void {
53
+ this.fullscreenHandler?.exitFullscreen();
54
+ FullscreenHandlerModule.onFullscreenChanged(
55
+ this.nativeId,
56
+ this.fullscreenHandler?.isFullscreenActive ?? false
57
+ );
58
+ }
59
+ }