@tivio/sdk-react 9.8.0 → 10.1.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/README.md CHANGED
@@ -8,9 +8,9 @@ settings in the administration of Tivio Studio while having the freedom to build
8
8
 
9
9
  Install @tivio/sdk-react along with its peer dependencies
10
10
  ```sh
11
- npm i react@17 react-dom@17 @tivio/sdk-react
11
+ npm i react@18 react-dom@18 @tivio/sdk-react
12
12
  # or
13
- yarn add react@17 react-dom@17 @tivio/sdk-react
13
+ yarn add react@18 react-dom@18 @tivio/sdk-react
14
14
  ```
15
15
 
16
16
  ## Initialization
@@ -63,337 +63,6 @@ const config: Config = {
63
63
  };
64
64
  ```
65
65
 
66
- ### Create user with email and password
67
-
68
- Returns user's firebase uid or null if error occurs
69
-
70
- ```typescript
71
- tivio.createUserWithEmailAndPassword(email: string, password: string, username: string, phoneNumber?: string | undefined): Promise<string | null>
72
- ```
73
- **Example**
74
- ```typescript
75
- await tivio.createUserWithEmailAndPassword('test@example.com', 'password', 'Fist & Last Name', '+420777123456')
76
- ```
77
-
78
- ### Sign in with email and password
79
-
80
- Returns user's firebase uid or null if error occurs
81
-
82
- ```typescript
83
- tivio.signInWithEmailAndPassword(email: string, password: string): Promise<string | null>
84
- ```
85
- **Example**
86
- ```typescript
87
- await tivio.signInWithEmailAndPassword('test@example.com', 'password')
88
- ```
89
-
90
- ### Sign out
91
-
92
- ```typescript
93
- tivio.signOut(): Promise<void>
94
- ```
95
-
96
- ### Reset password
97
-
98
- Send email with password reset link to the user with given email address
99
-
100
- ```typescript
101
- tivio.resetPassword(email: string): Promise<void>
102
- ```
103
-
104
- ## User entity
105
-
106
- ### Get user
107
-
108
- Returns user object or null if user is not authenticated
109
-
110
- ```typescript
111
- tivio.getUser(): Promise<User | null>
112
- ```
113
-
114
- ### Essential Properties
115
-
116
- #### Basic Information
117
- - `id: string` - Unique Tivio user ID
118
- - `email?: string` - Email address
119
- - `name?: string` - Display name - could be nickname, first name and last name, etc.
120
-
121
- #### Authentication Status
122
- - `isSignedIn: boolean` - Whether the user is currently signed in
123
- - `isReady: boolean` - Whether user data is fully loaded
124
- - `isAnonymous: boolean` - Whether this is an anonymous user
125
-
126
- #### Content & Preferences
127
- - `purchases: Purchase[]` - Active purchases (excluding vouchers)
128
-
129
- #### User Profiles
130
- - `profiles: UserProfile[]` - Available user profiles (e.g. multiple people can use the same user account with different preferences)
131
- - `activeUserProfileId: string | null` - Currently active profile
132
-
133
- ### Essential Methods
134
-
135
- #### Authentication
136
- ```typescript
137
- changePassword(oldPassword: string, newPassword: string): Promise<void>
138
- ```
139
-
140
- #### Profile Management
141
-
142
- #### Create user profile
143
-
144
- ```typescript
145
- interface CreateUserProfileRequest {
146
- /**
147
- * Profile name - typically first name and last name, but users can fill in whatever they want.
148
- */
149
- name: string
150
- /**
151
- * Filled in values for user profile survey. See {@link OrganizationDocument} its configuration.
152
- */
153
- survey?: ProfileSurvey
154
- }
155
- interface ProfileSurvey {
156
- gender?: Translation
157
- age?: AgeRange
158
- }
159
- interface AgeRange {
160
- from: number
161
- /**
162
- * If not set, we assume the range is not closed and {@property to} is set to infinity
163
- * (there are no values to represent {@link Infinity} in firestore, so we use undefined instead).
164
- */
165
- to?: number
166
- /**
167
- * If set, we assume that this is a profile for kids only (e.g. 0-12).
168
- * This value can only be `true` or not specified (undefined).
169
- */
170
- kidsOnly?: true
171
- }
172
- createUserProfile(request: CreateUserProfileRequest): Promise<void>
173
- ```
174
- **Example**
175
- ```typescript
176
- await user.createUserProfile({
177
- name: 'John Doe',
178
- survey: {
179
- gender: {
180
- cs: "Žena",
181
- en: "Female",
182
- sk: "Žena"
183
- },
184
- age: {
185
- from: 18,
186
- to: 24
187
- }
188
- }
189
- })
190
- ```
191
-
192
- #### Set active user profile
193
-
194
- ```typescript
195
- user.setActiveUserProfileId(profileId: string): void
196
- ```
197
-
198
- #### Delete user profile
199
-
200
- ```typescript
201
- user.deleteUserProfile(profileId: string): Promise<void>
202
- ```
203
-
204
- ## Content
205
-
206
- ### Tiles and assets
207
-
208
- A **tile** is a single item rendered inside a content row. The tile is the
209
- same entity the row points to (a `Video`, `Tag`, `TvChannel`, `Series`,
210
- `Article` or `Application`), so anything you can read off the entity (name,
211
- description, ids, monetization flags, …) is also available directly on the
212
- tile returned by `useItemsInRow` / `row.tiles`.
213
-
214
- Each tile carries several named image **assets** so the same entity can be
215
- rendered in different shapes without an extra fetch. Every named getter is a
216
- shortcut into the underlying `tile.assets` map — use the map directly if you
217
- need a custom asset that does not have a dedicated getter.
218
-
219
- **Shared across all entity types**
220
-
221
- - `tile.landscape` — 16:9 thumbnail (standard rows)
222
- - `tile.portrait` — 2:3 poster (portrait rows)
223
- - `tile.circled` — 1:1 circular crop (e.g. creators)
224
- - `tile.square` — 1:1 square (square rows)
225
- - `tile.banner` — 16:9 hero banner (banner rows / top of screens)
226
- - `tile.bannerMobile` — narrow 16:9 banner variant for phones
227
- - `tile.assets` — raw `{ [assetName]: ScalableAsset }` map behind the getters
228
-
229
- **Extras by entity type (on top of the shared set above)**
230
-
231
- - `Video` — `detailBanner` (backdrop above the player), `image`, `cover`
232
- (deprecated, kept for compatibility), `backgroundBlurBannerMobile`
233
- - `Tag` / `Article` / `Series` — `detailBanner`, `cover`
234
- - `TvChannel` — `logo`, `logoPendingOverlayWidth`, `cover`
235
- - `Application` — read via `tile.application`: `logo`, `logoLandscape`,
236
- `profilePhoto`
237
-
238
- If an asset is not set on the entity, the getter returns `null` rather than
239
- throwing, so it's safe to fall back from a specific variant to a more
240
- generic one (for example `tile.landscape ?? tile.banner ?? tile.cover`).
241
-
242
- A live example that renders the same video through every getter is included
243
- in `examples/sdk-react/src/index.tsx` (`createAssetsShowcaseDemo`).
244
-
245
- ### Get content based on user profile
246
-
247
- ```typescript
248
- interface GetUserProfileDataOptions {
249
- /**
250
- * If true, the data will be returned for all user profiles.
251
- * If false, the data will be returned only for the active user profile.
252
- */
253
- ignoreActiveUserProfile?: boolean
254
- }
255
-
256
- getFavorites(options?: GetUserProfileDataOptions): Promise<FavoriteWithData[]>
257
- getWatchPositions(options?: GetUserProfileDataOptions): Promise<WatchPositionWithData[]>
258
- getWatchHistory(options?: GetUserProfileDataOptions): Promise<WatchPositionWithData[]>
259
- ```
260
-
261
- ### Get user favorites
262
-
263
- ```typescript
264
- interface FavoriteWithData {
265
- content: Video | Tag
266
- type: 'video' | 'tag' // Filtering by this type will set the content type to be Video or Tag
267
- profileId?: string
268
- }
269
-
270
- getFavorites(options?: GetUserProfileDataOptions): Promise<FavoriteWithData[]>
271
- ```
272
- **Example**
273
- ```typescript
274
- const favorites = await user.getFavorites()
275
- favorites.forEach(favorite => {
276
- console.log({
277
- name: favorite.name,
278
- cover: favorite.cover, // cover image (landscape)
279
- portrait: favorite.portrait, // portrait image (portrait)
280
- })
281
- })
282
- ```
283
-
284
- ### Get user watch positions
285
-
286
- ```typescript
287
- interface WatchPositionWithData {
288
- position: number
289
- video: Video
290
- tag?: Tag
291
- episodeNumber?: number
292
- seasonNumber?: number
293
- videoDuration?: number
294
- }
295
-
296
- getWatchPositions(options?: GetUserProfileDataOptions): Promise<WatchPositionWithData[]>
297
- ```
298
- **Example**
299
- ```typescript
300
- const watchPositions = await user.getWatchPositions()
301
- watchPositions.forEach(watchPosition => {
302
- console.log({
303
- position: watchPosition.position, // watch position in milliseconds
304
- videoName: watchPosition.video.name,
305
- videoCover: watchPosition.video.cover,
306
- tagName: watchPosition.tag?.name, // optional tag for series
307
- episodeNumber: watchPosition.episodeNumber, // optional episode number
308
- seasonNumber: watchPosition.seasonNumber, // optional season number
309
- videoDuration: watchPosition.videoDuration, // optional video duration
310
- })
311
- })
312
- ```
313
-
314
- ### Get user watch history
315
-
316
- ```typescript
317
- getWatchHistory(options?: GetUserProfileDataOptions): Promise<WatchPositionWithData[]>
318
- ```
319
- **Example**
320
- ```typescript
321
- const watchHistory = await user.getWatchHistory()
322
- watchHistory.forEach(watchPosition => {
323
- console.log({
324
- position: watchPosition.position, // watch position in milliseconds
325
- videoName: watchPosition.video.name,
326
- videoCover: watchPosition.video.cover,
327
- tagName: watchPosition.tag?.name, // optional tag for series
328
- episodeNumber: watchPosition.episodeNumber, // optional episode number
329
- seasonNumber: watchPosition.seasonNumber, // optional season number
330
- videoDuration: watchPosition.videoDuration, // optional video duration
331
- })
332
- })
333
- ```
334
-
335
- ### Add to/remove from favorites
336
-
337
- Simply call addToFavorites or removeFromFavorites on the Video or Tag.
338
- ```typescript
339
- const video = await tivio.getVideoById('videoId')
340
- await video.addToFavorites()
341
- await video.removeFromFavorites()
342
-
343
- const tag = await tivio.getTagById('tagId')
344
- await tag.addToFavorites()
345
- await tag.removeFromFavorites()
346
-
347
- // Remove a favorite from favorites
348
- const favorites = await tivio.getUser()?.favorites
349
- favorites[0]?.removeFromFavorites()
350
- ```
351
-
352
- ### Add to/remove from favorites by path
353
-
354
- You can also add or remove favorites using content paths directly:
355
- ```typescript
356
- // Add to favorites by path
357
- await tivio.addToFavoritesByPath('videos/videoId')
358
- await tivio.addToFavoritesByPath('tags/tagId')
359
-
360
- // Remove from favorites by path
361
- await tivio.removeFromFavoritesByPath('videos/videoId')
362
- await tivio.removeFromFavoritesByPath('tags/tagId')
363
- ```
364
-
365
- > ℹ️ **_Note:_** When user saves favorite without profileId, it will only be shown if the app doesn't have any active user profile.
366
-
367
- ### Get screen by ID
368
-
369
- ```typescript
370
- getScreenById(screenId: string): Promise<Screen | null>
371
- ```
372
-
373
- ### Get row by ID
374
-
375
- ```typescript
376
- getRowById(rowId: string): Promise<Row | null>
377
- ```
378
-
379
- ### Get video by ID
380
-
381
- ```typescript
382
- getVideoById(videoId: string): Promise<Video | null>
383
- ```
384
-
385
- ### Get tag by ID
386
-
387
- ```typescript
388
- getTagById(tagId: string): Promise<Tag | null>
389
- ```
390
-
391
- ### Get TV Channel by ID
392
-
393
- ```typescript
394
- getTvChannelById(tvChannelId: string): Promise<TvChannel | null>
395
- ```
396
-
397
66
  ## Player
398
67
 
399
68
  You can choose whether you will use complete player component provided by Tivio or you will wrap your existing player
@@ -629,64 +298,10 @@ The VideoController returned by `renderWebPlayer` provides the following methods
629
298
 
630
299
  The Tivio player supports different types of sources:
631
300
 
632
- **PathSourceParams** - Path-based sources with ad configuration
633
- - Used for playing Tivio-hosted videos and TV channels with custom VAST ad configurations
634
- - Supports both `videos/ID` and `tvChannels/ID` paths
635
- - Includes `staticAdsConfig` for custom VAST ad insertion
636
- - Example usage:
637
- ```typescript
638
- const sourceWithAds = {
639
- path: 'videos/123', // or 'tvChannels/456'
640
- staticAdsConfig: [
641
- {
642
- type: 'preroll',
643
- url: 'https://example.com/vast-preroll-ad.xml',
644
- },
645
- {
646
- type: 'midroll',
647
- from: 30000, // 30 seconds
648
- url: 'https://example.com/vast-midroll-ad.xml',
649
- },
650
- {
651
- type: 'postroll',
652
- url: 'https://example.com/vast-postroll-ad.xml',
653
- },
654
- ],
655
- }
656
- ```
657
-
658
- **VOD_TIVIO** - Tivio-hosted video-on-demand content
659
- - Used for playing videos that are hosted within Tivio's infrastructure
660
- - Example usage:
661
- ```typescript
662
- const vodSource = {
663
- type: SourceType.VOD_TIVIO,
664
- videoPath: 'videos/123', // Video path
665
- sourcePlayMode: SourcePlayMode.ON_DEMAND,
666
- name: 'Tivio Video',
667
- autoplay: false,
668
- continuePositionMs: 10000, // Start at 10 seconds (optional)
669
- }
670
- ```
671
-
672
- **CHANNEL** - TV channel content (both classic and virtual channels)
673
- - Used for playing live TV channels
674
- - Example usage:
675
- ```typescript
676
- const channelSource = {
677
- type: SourceType.CHANNEL,
678
- path: 'tvChannels/456', // TV channel path
679
- sourcePlayMode: SourcePlayMode.LIVE,
680
- name: 'TV Channel',
681
- autoplay: true,
682
- }
683
- ```
684
-
685
301
  **VOD_EXTERNAL** - External video-on-demand content from third-party URLs
686
302
  - Used for playing videos that are hosted outside of Tivio's infrastructure
687
303
  - Supports various streaming protocols (HLS, DASH, MP4)
688
- - Optionally, you can include `staticAdsConfig` for custom ad insertion
689
- - Example usage (single url):
304
+ - Example usage:
690
305
  ```typescript
691
306
  const externalSource = {
692
307
  type: SourceType.VOD_EXTERNAL,
@@ -696,137 +311,9 @@ const externalSource = {
696
311
  name: 'External Video',
697
312
  autoplay: false,
698
313
  continuePositionMs: 10000, // Start at 10 seconds (optional)
699
- staticAdsConfig: [
700
- {
701
- type: 'preroll',
702
- url: 'https://example.com/preroll-ad.xml',
703
- },
704
- {
705
- type: 'midroll',
706
- from: 20000, // 20 seconds
707
- url: 'https://example.com/midroll-ad.xml',
708
- },
709
- ],
710
- }
711
- ```
712
-
713
- **Path-Based Sources**
714
- You can also use simple path strings for both video and TV channel sources:
715
- ```typescript
716
- // Video path
717
- videoController.setSource('videos/123')
718
-
719
- // TV channel path
720
- videoController.setSource('tvChannels/456')
721
- ```
722
-
723
- #### Static Ads Configuration
724
-
725
- > ℹ️ **_Note:_** To enable ad functionality, you must configure the IMA ad service in your Tivio configuration:
726
- > ```typescript
727
- > import { AD_SERVICE_PROXY_NAME } from '@tivio/sdk-react'
728
- >
729
- > const config = {
730
- > // ... other config properties
731
- > player: {
732
- > adService: {
733
- > name: AD_SERVICE_PROXY_NAME.IMA,
734
- > },
735
- > },
736
- > }
737
- > ```
738
-
739
- The `staticAdsConfig` property lets you specify custom ad insertion points within your content.
740
- If multiple ads are set for the same entry point, they will be played one after another in sequence. For example, if you have two preroll ads, the first will play, followed by the second. Similarly, midroll ads that share the same `from` time will be grouped and played sequentially.
741
-
742
- It supports the following ad types:
743
-
744
- **Preroll Ads** - Play before the main content starts
745
- ```typescript
746
- {
747
- type: 'preroll',
748
- url: 'https://example.com/preroll-ad.xml',
749
314
  }
750
315
  ```
751
316
 
752
- **Midroll Ads** - Play during the main content at specified time
753
- ```typescript
754
- {
755
- type: 'midroll',
756
- from: 30000, // Time in milliseconds (30 seconds)
757
- url: 'https://example.com/midroll-ad.xml',
758
- }
759
- ```
760
-
761
- **Postroll Ads** - Play after the main content ends
762
- ```typescript
763
- {
764
- type: 'postroll',
765
- url: 'https://example.com/postroll-ad.xml',
766
- }
767
- ```
768
-
769
- **Complete Example:**
770
- ```typescript
771
- const sourceWithAds = {
772
- path: 'videos/123',
773
- name: 'Video with Multiple Ad Types',
774
- staticAdsConfig: [
775
- // multiple preroll ads
776
- {
777
- type: 'preroll',
778
- url: 'https://vasterix.joj.sk/api/v1/creative?id=0c5d96fd-2ab9-4207-a325-4607437965e3&vast=4.0',
779
- },
780
- {
781
- type: 'preroll',
782
- url: 'https://example.com/preroll-ad.xml',
783
- },
784
- // multiple midroll ads (with same from time)
785
- {
786
- type: 'midroll',
787
- from: 30000, // 30 seconds
788
- url: 'https://example.com/midroll-ad-1.xml',
789
- },
790
- {
791
- type: 'midroll',
792
- from: 30000, // 30 seconds
793
- url: 'https://example.com/midroll-ad-2.xml',
794
- },
795
- // one midroll ad with different from time
796
- {
797
- type: 'midroll',
798
- from: 60000, // 1 minute
799
- url: 'https://example.com/midroll-ad-2.xml',
800
- },
801
- // postroll ad
802
- {
803
- type: 'postroll',
804
- url: 'https://example.com/postroll-ad.xml',
805
- },
806
- ],
807
- }
808
- ```
809
- - It is also possible to set multiple urls for external source type, e.g. when you want to use both DASH and HLS formats. Player then automatically will pick most suitable format to play depending on device capabilities.
810
- - Example usage (multiple urls):
811
- ```typescript
812
- const externalSource = {
813
- type: SourceType.VOD_EXTERNAL,
814
- urls: ['https://example.com/video.m3u8', 'https://example.com/video.mpd'],
815
- sourcePlayMode: SourcePlayMode.ON_DEMAND,
816
- }
817
- ```
818
-
819
- **VOD_TIVIO** - Internal source type for playing content managed by Tivio
820
- - Used for playing videos and tv channels managed by Tivio infrastructure, e.g. everything that is uploaded through Tivio Admin application
821
- - For convenience, it is recommended to use shortcut format and pass the source as string in `${'videos' | 'tvChannels'}/{id}` format
822
- - Ids of corresponding videos and tv channel could be found in Tivio admin application
823
- - Example usage:
824
- ```typescript
825
- const tivioVideoSource = 'videos/2BzH4xsTXW8vqYKJegXj'
826
- const tivioTvChannelSource = 'tvChannels/Ct4UcK6ozX3VfxaL5yP5'
827
- ```
828
- - Otherwise, it is also possible to pass internal tivio source as an object with `type: SourceType.VOD_TIVIO` and additional parameters, similarly to external source type.
829
-
830
317
  #### Source Play Modes
831
318
 
832
319
  The `sourcePlayMode` property determines how the content is played:
@@ -835,72 +322,6 @@ The `sourcePlayMode` property determines how the content is played:
835
322
  - **LIVE** - Live stream mode with no seeking
836
323
  - **HYBRID** - Combines LIVE with seeking
837
324
 
838
- #### Player Authentication Configuration
839
-
840
- Use `disableTvChannelsForAnonymousUsers` property to show overlay to sign in when user is anonymous and tries to play a TV channel (free or monetized).
841
- Set the property to `true` inside the `player` property in Tivio config.
842
-
843
- Tivio config:
844
- ```typescript
845
- const config = {
846
- // ... other config properties
847
- player: {
848
- // ... other player properties
849
- disableTvChannelsForAnonymousUsers: true, // or false
850
- },
851
- }
852
- ```
853
-
854
- #### User Authentication Callbacks
855
-
856
- The `userAuthCallbacks` property allows you to handle user authentication flows when the player requires user login or registration. This is particularly useful for content that requires authentication (e.g., premium content).
857
-
858
- ```typescript
859
- interface UserAuthCallbacks {
860
- onGoToLogin: () => void
861
- onGoToRegistration: () => void
862
- }
863
- ```
864
-
865
- **Example Implementation:**
866
-
867
- ```typescript
868
- // Usage with renderWebPlayer
869
- const videoController = await renderWebPlayer(
870
- document.getElementById('video-player'),
871
- {
872
- id: 'player-main',
873
- source: 'videos/PREMIUM_VIDEO_ID',
874
- userAuthCallbacks: {
875
- onGoToLogin: () => {
876
- // Show your login modal
877
- setShowLoginModal(true)
878
- },
879
- onGoToRegistration: () => {
880
- // Show your registration modal
881
- setShowRegistrationModal(true)
882
- },
883
- },
884
- }
885
- )
886
-
887
- // Handle login in your modal
888
- const handleLogin = async (email: string, password: string) => {
889
- try {
890
- await tivio.signInWithEmailAndPassword(email, password)
891
- console.log('Login successful')
892
- } catch (error) {
893
- console.error('Login failed:', error)
894
- throw error
895
- }
896
- }
897
- ```
898
-
899
- **When are these callbacks triggered?**
900
-
901
- - **`onGoToLogin`**: Called when the player is trying to play content behind a paywall and user clicks on the login button in the overlay
902
- - **`onGoToRegistration`**: Called when the player is trying to play content behind a paywall and user clicks on the registration button in the overlay
903
-
904
325
  #### Using setSource with VideoController
905
326
 
906
327
  The `setSource` method allows you to dynamically change what's playing:
@@ -909,23 +330,6 @@ The `setSource` method allows you to dynamically change what's playing:
909
330
  // Change to a different video
910
331
  videoController.setSource('videos/newVideoId')
911
332
 
912
- // Change to a video with custom ads
913
- videoController.setSource({
914
- path: 'videos/newVideoId',
915
- name: 'Video with Ads',
916
- staticAdsConfig: [
917
- {
918
- type: 'preroll',
919
- url: 'https://example.com/preroll-ad.xml',
920
- },
921
- {
922
- type: 'midroll',
923
- from: 30000,
924
- url: 'https://example.com/midroll-ad.xml',
925
- },
926
- ],
927
- })
928
-
929
333
  // Change to an external video
930
334
  videoController.setSource({
931
335
  type: SourceType.VOD_EXTERNAL,
@@ -937,18 +341,6 @@ videoController.setSource({
937
341
  // Change to a TV channel
938
342
  videoController.setSource('tvChannels/channelId')
939
343
 
940
- // Change to a TV channel with custom ads
941
- videoController.setSource({
942
- path: 'tvChannels/channelId',
943
- name: 'TV Channel with Ads',
944
- staticAdsConfig: [
945
- {
946
- type: 'preroll',
947
- url: 'https://example.com/tv-preroll-ad.xml',
948
- },
949
- ],
950
- })
951
-
952
344
  // Stop playback
953
345
  videoController.setSource(null)
954
346
  ```
@@ -958,238 +350,17 @@ The `setSource` method is particularly useful for:
958
350
  - Implementing playlists
959
351
  - Dynamic content loading
960
352
 
961
- #### Minimal OSD / Sticky Mini Player
962
-
963
- The player supports a minimal OSD mode designed for small or pinned containers such as a
964
- scroll-fixed "mini" player. When enabled, only the essential controls are rendered:
965
-
966
- - `play` / `pause` (small button in the bottom-left corner)
967
- - `volume`
968
- - `fullscreen`
969
-
970
- The mode can be toggled in two ways:
971
-
972
- 1. Declaratively, via the `isMinimal` prop on `WebPlayerProps`.
973
- 2. Imperatively, via the vanilla controller's `setIsMinimal(isMinimal: boolean)` method — useful
974
- for switching on/off at runtime (e.g. when the player becomes pinned after the user scrolls
975
- past it).
976
-
977
- **Example: scroll-pinned mini player**
978
-
979
- The layout is driven entirely by CSS — the JS only toggles an `is-floating` class and calls
980
- `setIsMinimal(true|false)` accordingly. Responsive behavior (desktop corner vs. mobile
981
- top-full-width) is handled with a media query, so there is no need to watch viewport size from
982
- React/JS.
983
-
984
- ```html
985
- <section class="mini-player-demo">
986
- <div class="mini-player-anchor">
987
- <div class="mini-player" id="mini-player"></div>
988
- </div>
989
- <!-- ...page content... -->
990
- </section>
991
- ```
992
-
993
- ```css
994
- .mini-player-anchor {
995
- position: relative;
996
- width: 100%;
997
- aspect-ratio: 16 / 9;
998
- }
999
-
1000
- .mini-player {
1001
- position: absolute;
1002
- inset: 0;
1003
- background: #000;
1004
- }
1005
-
1006
- /* Floating (desktop): pinned to bottom-right. */
1007
- .mini-player.is-floating {
1008
- position: fixed;
1009
- inset: auto 24px 24px auto;
1010
- width: 360px;
1011
- aspect-ratio: 16 / 9;
1012
- z-index: 1000;
1013
- border-radius: 8px;
1014
- overflow: hidden;
1015
- box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
1016
- }
1017
-
1018
- /* Floating (narrow viewports): pinned to top, full width. */
1019
- @media (max-width: 600px) {
1020
- .mini-player.is-floating {
1021
- inset: 0 0 auto 0;
1022
- width: 100%;
1023
- border-radius: 0;
1024
- }
1025
- }
1026
- ```
1027
-
1028
- ```ts
1029
- import { renderWebPlayer } from '@tivio/sdk-react'
1030
-
1031
- const container = document.getElementById('mini-player')!
1032
- const anchor = document.querySelector('.mini-player-anchor')!
1033
-
1034
- const controller = await renderWebPlayer(container, {
1035
- id: 'mini-player',
1036
- source: 'videos/YOUR_VIDEO_ID',
1037
- isSameSizeAsParent: true,
1038
- isMutedByDefault: true,
1039
- autoplay: true,
1040
- })
1041
-
1042
- // Pop out of the flow once the inline anchor leaves the viewport,
1043
- // and switch the OSD between minimal and default accordingly.
1044
- new IntersectionObserver(
1045
- ([entry]) => {
1046
- const isFloating = !entry.isIntersecting
1047
- container.classList.toggle('is-floating', isFloating)
1048
- controller.setIsMinimal(isFloating)
1049
- },
1050
- { threshold: 0.1 },
1051
- ).observe(anchor)
1052
- ```
1053
-
1054
- Notes:
1055
- - The anchor stays in the flow with its original 16:9 size, so the page layout does not jump
1056
- when the player detaches.
1057
- - The desktop/mobile floating layout is fully CSS-driven; resizing the browser window
1058
- updates it live with no extra JS.
1059
-
1060
- You can see this pattern in action in `examples/sdk-react/src/index.tsx` (the
1061
- `createStickyMiniPlayerDemo` function) with styles in `examples/sdk-react/src/index.css`.
1062
-
1063
353
  **Event Handling:**
1064
354
  - `addEventListener(event: string, callback: (value: T) => void)` - Add event listener
1065
355
  - `removeEventListener(event: string, callback: (value: T) => void)` - Remove event listener
1066
356
 
1067
357
  **Utility:**
1068
- - `setIsMinimal(isMinimal: boolean)` - Toggle the minimal OSD mode at runtime (see
1069
- [Minimal OSD / Sticky Mini Player](#minimal-osd--sticky-mini-player))
1070
358
  - `destroy()` - Destroy player and clean up resources
1071
359
 
1072
360
  #### Playback Events
1073
361
 
1074
362
  The VideoController emits various events that you can listen to:
1075
363
 
1076
- #### Ad events
1077
-
1078
- ```typescript
1079
- videoController.addEventListener('ad-started', (adMetadata: AdMetadata | null) => {
1080
- if (!adMetadata) {
1081
- console.log('Ad started playing (no metadata available)')
1082
- return
1083
- }
1084
-
1085
- console.log('Ad started playing', adMetadata)
1086
-
1087
- if ('customAdMetadata' in adMetadata && adMetadata.customAdMetadata) {
1088
- console.log('Ad custom metadata:', adMetadata.customAdMetadata)
1089
- // customAdMetadata contains VAST trafficking parameters (from VAST AdParameters tag)
1090
- }
1091
-
1092
- // Access CTA element for rendering custom call-to-action buttons
1093
- const { ctaElement } = adMetadata
1094
- if (ctaElement) {
1095
- console.log('CTA element available for rendering custom buttons', ctaElement)
1096
-
1097
- const { customAdMetadata } = adMetadata
1098
- if (!customAdMetadata) {
1099
- console.log('No custom ad metadata available')
1100
- return
1101
- }
1102
-
1103
- const { extensions } = customAdMetadata
1104
- if (!extensions) {
1105
- console.log('No extensions available')
1106
- return
1107
- }
1108
-
1109
- const { parameters } = extensions[0]
1110
- if (!parameters) {
1111
- console.log('No parameters available')
1112
- return
1113
- }
1114
-
1115
- const metadataParameters = parameters as {
1116
- main_title?: string
1117
- subtitle?: string
1118
- image?: string
1119
- button_text: string
1120
- url: string
1121
- }
1122
-
1123
- const buttonText = metadataParameters.button_text as string | undefined
1124
-
1125
- if (!buttonText) {
1126
- console.log('No button text available')
1127
- return
1128
- }
1129
-
1130
- // Example: Create a custom CTA button using React portal
1131
- const CTAButton = () => (
1132
- <div style={{
1133
- position: 'absolute',
1134
- bottom: '20px',
1135
- right: '20px',
1136
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
1137
- color: 'white',
1138
- padding: '12px 24px',
1139
- borderRadius: '6px',
1140
- cursor: 'pointer',
1141
- fontSize: '16px',
1142
- fontWeight: 'bold',
1143
- pointerEvents: 'auto',
1144
- }}>
1145
- Learn More
1146
- </div>
1147
- )
1148
-
1149
- // Render the CTA button using React portal
1150
- const root = ReactDOM.createRoot(ctaElement)
1151
- root.render(<CTAButton />)
1152
- }
1153
-
1154
- // adMetadata contains information like:
1155
- // - ctaElement?: HTMLElement (for rendering custom CTA buttons)
1156
- // - customAdMetadata?: Record<string, unknown> (for IMA ads with rich metadata)
1157
- // - type: 'ad'
1158
- // - subType: 'inserted' | 'original'
1159
- // - secondsToEnd: number
1160
- // - secondsToSkippable: number | null
1161
- // - canTriggerSkip: boolean
1162
- // - isSkippable: boolean
1163
- // - order: number | null
1164
- // - totalCount: number | null
1165
- // - skip: () => void
1166
- // Update UI, show ad overlay, etc.
1167
- })
1168
-
1169
- videoController.addEventListener('ad-ended', () => {
1170
- console.log('Ad finished playing')
1171
- })
1172
-
1173
- // Companion ads event - fires when companion ads are available for the current ad
1174
- videoController.addEventListener('companion-ads', (companionAds) => {
1175
- console.log('Companion ads available:', companionAds)
1176
-
1177
- // Access companion ad properties using IMA methods
1178
- companionAds.forEach((companionAd, index) => {
1179
- console.log(`Companion Ad ${index + 1}:`, {
1180
- width: companionAd.getWidth(),
1181
- height: companionAd.getHeight(),
1182
- contentType: companionAd.getContentType(),
1183
- // HTML content as string
1184
- content: companionAd.getContent()
1185
- })
1186
- })
1187
- })
1188
- ```
1189
-
1190
- > ℹ️ **_Note:_** The CTA overlay element is visible in the WebPlayer only while an ad is playing and is automatically cleaned up on source changes.
1191
-
1192
- #### Video state changes
1193
364
  ```typescript
1194
365
  // Video state changes
1195
366
  videoController.addEventListener('statechange', (state) => {
@@ -1267,11 +438,6 @@ The `WebPlayerProps` interface defines the properties that can be passed to the
1267
438
  - **`showTvStreamType`** (optional): Whether to show TV stream type indicator
1268
439
  - **`showCookiesSettings`** (optional): Whether to show cookies settings
1269
440
  - **`showOsd`** (optional, default: `true`): Whether to show the On-Screen Display (OSD)
1270
- - **`isMinimal`** (optional, default: `false`): If `true`, the player uses a minimal OSD that
1271
- exposes only `play`/`pause`, `volume` and `fullscreen` controls. A small center play/pause
1272
- icon flashes briefly when the state toggles and then fades out.
1273
- Designed for small/pinned containers such as scroll-fixed mini players. See
1274
- [Minimal OSD / Sticky Mini Player](#minimal-osd--sticky-mini-player) for usage details.
1275
441
  - **`showBufferingSpinner`** (optional, default: `true`): Whether to show buffering spinner
1276
442
 
1277
443
  ### Audio Properties
@@ -1282,7 +448,7 @@ The `WebPlayerProps` interface defines the properties that can be passed to the
1282
448
 
1283
449
  - **`enableKeyboardShortcuts`** (optional, default: `true`): Whether to enable keyboard shortcuts
1284
450
  - **`customShortcuts`** (optional): Custom keyboard shortcuts configuration:
1285
- ```typescript
451
+ ```typescript
1286
452
  {
1287
453
  toggleFullscreen: number[], // Array of key codes
1288
454
  togglePause: number[],
@@ -1292,7 +458,7 @@ The `WebPlayerProps` interface defines the properties that can be passed to the
1292
458
  volumeUp: number[],
1293
459
  volumeDown: number[]
1294
460
  }
1295
- ```
461
+ ```
1296
462
 
1297
463
  ### Ad Block Properties
1298
464