@brightcove/components-embed-code-modal 1.0.7 → 1.0.8

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 (181) hide show
  1. package/.eslintignore +7 -0
  2. package/.eslintrc +23 -0
  3. package/.github/workflows/ci.yml +23 -0
  4. package/.husky/pre-commit +4 -0
  5. package/.nvmrc +1 -0
  6. package/.prettierignore +7 -0
  7. package/.prettierrc +5 -0
  8. package/.release-it.json +40 -0
  9. package/.whitesource +3 -0
  10. package/CHANGELOG.md +387 -0
  11. package/CODEOWNERS +37 -0
  12. package/PULL_REQUEST_TEMPLATE.md +17 -0
  13. package/__tests__/EmbedCode.test.tsx +51 -0
  14. package/__tests__/InteractivityProject.test.tsx +50 -0
  15. package/__tests__/PlayerPreview.test.tsx +23 -0
  16. package/__tests__/TabsHeader.test.tsx +19 -0
  17. package/__tests__/TabsSection.test.tsx +46 -0
  18. package/__tests__/TitleWithTooltip.test.tsx +45 -0
  19. package/__tests__/VideoPlayer.test.tsx +38 -0
  20. package/__tests__/embedCodeGenerator.test.ts +111 -0
  21. package/__tests__/util.test.ts +89 -0
  22. package/babel.config.json +17 -0
  23. package/build/README.md +109 -0
  24. package/build/package.json +100 -0
  25. package/etc/wildcard.brightcove.com.key +27 -0
  26. package/etc/wildcard.brightcove.com.pem +96 -0
  27. package/jest/jest.setup.js +62 -0
  28. package/jest/jest.stub.js +4 -0
  29. package/jest.config.js +24 -0
  30. package/package.json +15 -5
  31. package/scripts/create-dist-pkg-prod.js +18 -0
  32. package/src/index.ts +17 -0
  33. package/src/js/EmbedCodeModal.tsx +105 -0
  34. package/src/js/components/CustomSettingsDropdown.tsx +184 -0
  35. package/src/js/components/EmbedCopySection.tsx +130 -0
  36. package/src/js/components/ModalContainer.tsx +367 -0
  37. package/src/js/components/NoPlayerModal.tsx +42 -0
  38. package/src/js/components/PlayerPreview.tsx +116 -0
  39. package/src/js/components/TabsHeader.tsx +31 -0
  40. package/src/js/components/TabsSection.tsx +74 -0
  41. package/src/js/components/TitleWithTooltip.tsx +39 -0
  42. package/src/js/components/VideoLinks.tsx +83 -0
  43. package/src/js/components/VideoPlayer.tsx +324 -0
  44. package/src/js/components/index.ts +9 -0
  45. package/src/js/components/prism/PrismLiveModal.tsx +162 -0
  46. package/src/js/components/prism/PrismPlayerControls.tsx +343 -0
  47. package/src/js/components/prism/PrismPlayerPreview.tsx +98 -0
  48. package/src/js/components/prism/index.ts +3 -0
  49. package/src/js/constants/baseUrls.ts +4 -0
  50. package/src/js/constants/embed.ts +12 -0
  51. package/src/js/constants/player.ts +32 -0
  52. package/src/js/constants/pluginsRegistry.ts +30 -0
  53. package/src/js/hooks/useGetPlayers.ts +12 -0
  54. package/src/js/i18n.ts +43 -0
  55. package/src/js/services/players-api.ts +26 -0
  56. package/src/js/services/url-shortener.ts +10 -0
  57. package/src/js/types/EmbedCodeModalProps.ts +72 -0
  58. package/src/js/types/EmbedCodeProps.ts +13 -0
  59. package/src/js/types/brightcoveEmbedCode.d.ts +18 -0
  60. package/src/js/types/players.ts +64 -0
  61. package/src/js/types/react-i18next.d.ts +8 -0
  62. package/src/js/types/studioModule.ts +7 -0
  63. package/src/js/utils/addPluginsDataToPlayers.ts +59 -0
  64. package/src/js/utils/call.ts +31 -0
  65. package/src/js/utils/dimensions.ts +53 -0
  66. package/src/js/utils/embedCodeGenerator.ts +120 -0
  67. package/src/js/utils/featureSwitches.ts +16 -0
  68. package/src/js/utils/util.ts +153 -0
  69. package/src/module.tsx +280 -0
  70. package/src/styles/CustomSettingsDropdown.sass +42 -0
  71. package/src/styles/EmbedCode.sass +30 -0
  72. package/src/styles/EmbedCodeModal.sass +76 -0
  73. package/src/styles/base.sass +1 -0
  74. package/src/styles/prism/PrismLiveModal.sass +155 -0
  75. package/src/styles/theme.sass +11 -0
  76. package/src/styles/typings.td.ts +9 -0
  77. package/src/translations/en.json +61 -0
  78. package/src/translations/es.json +61 -0
  79. package/src/translations/fr.json +61 -0
  80. package/src/translations/ja.json +61 -0
  81. package/src/translations/ko.json +61 -0
  82. package/src/translations/zh.json +61 -0
  83. package/src/types/studio.ts +197 -0
  84. package/tsconfig.json +34 -0
  85. package/webpack.config.js +117 -0
  86. /package/{__tests__ → build/__tests__}/EmbedCode.test.d.ts +0 -0
  87. /package/{__tests__ → build/__tests__}/EmbedCode.test.d.ts.map +0 -0
  88. /package/{__tests__ → build/__tests__}/InteractivityProject.test.d.ts +0 -0
  89. /package/{__tests__ → build/__tests__}/InteractivityProject.test.d.ts.map +0 -0
  90. /package/{__tests__ → build/__tests__}/PlayerPreview.test.d.ts +0 -0
  91. /package/{__tests__ → build/__tests__}/PlayerPreview.test.d.ts.map +0 -0
  92. /package/{__tests__ → build/__tests__}/TabsHeader.test.d.ts +0 -0
  93. /package/{__tests__ → build/__tests__}/TabsHeader.test.d.ts.map +0 -0
  94. /package/{__tests__ → build/__tests__}/TabsSection.test.d.ts +0 -0
  95. /package/{__tests__ → build/__tests__}/TabsSection.test.d.ts.map +0 -0
  96. /package/{__tests__ → build/__tests__}/TitleWithTooltip.test.d.ts +0 -0
  97. /package/{__tests__ → build/__tests__}/TitleWithTooltip.test.d.ts.map +0 -0
  98. /package/{__tests__ → build/__tests__}/VideoPlayer.test.d.ts +0 -0
  99. /package/{__tests__ → build/__tests__}/VideoPlayer.test.d.ts.map +0 -0
  100. /package/{__tests__ → build/__tests__}/embedCodeGenerator.test.d.ts +0 -0
  101. /package/{__tests__ → build/__tests__}/embedCodeGenerator.test.d.ts.map +0 -0
  102. /package/{__tests__ → build/__tests__}/util.test.d.ts +0 -0
  103. /package/{__tests__ → build/__tests__}/util.test.d.ts.map +0 -0
  104. /package/{index.js → build/index.js} +0 -0
  105. /package/{index.js.map → build/index.js.map} +0 -0
  106. /package/{src → build/src}/index.d.ts +0 -0
  107. /package/{src → build/src}/index.d.ts.map +0 -0
  108. /package/{src → build/src}/js/EmbedCodeModal.d.ts +0 -0
  109. /package/{src → build/src}/js/EmbedCodeModal.d.ts.map +0 -0
  110. /package/{src → build/src}/js/components/CustomSettingsDropdown.d.ts +0 -0
  111. /package/{src → build/src}/js/components/CustomSettingsDropdown.d.ts.map +0 -0
  112. /package/{src → build/src}/js/components/EmbedCopySection.d.ts +0 -0
  113. /package/{src → build/src}/js/components/EmbedCopySection.d.ts.map +0 -0
  114. /package/{src → build/src}/js/components/ModalContainer.d.ts +0 -0
  115. /package/{src → build/src}/js/components/ModalContainer.d.ts.map +0 -0
  116. /package/{src → build/src}/js/components/NoPlayerModal.d.ts +0 -0
  117. /package/{src → build/src}/js/components/NoPlayerModal.d.ts.map +0 -0
  118. /package/{src → build/src}/js/components/PlayerPreview.d.ts +0 -0
  119. /package/{src → build/src}/js/components/PlayerPreview.d.ts.map +0 -0
  120. /package/{src → build/src}/js/components/TabsHeader.d.ts +0 -0
  121. /package/{src → build/src}/js/components/TabsHeader.d.ts.map +0 -0
  122. /package/{src → build/src}/js/components/TabsSection.d.ts +0 -0
  123. /package/{src → build/src}/js/components/TabsSection.d.ts.map +0 -0
  124. /package/{src → build/src}/js/components/TitleWithTooltip.d.ts +0 -0
  125. /package/{src → build/src}/js/components/TitleWithTooltip.d.ts.map +0 -0
  126. /package/{src → build/src}/js/components/VideoLinks.d.ts +0 -0
  127. /package/{src → build/src}/js/components/VideoLinks.d.ts.map +0 -0
  128. /package/{src → build/src}/js/components/VideoPlayer.d.ts +0 -0
  129. /package/{src → build/src}/js/components/VideoPlayer.d.ts.map +0 -0
  130. /package/{src → build/src}/js/components/index.d.ts +0 -0
  131. /package/{src → build/src}/js/components/index.d.ts.map +0 -0
  132. /package/{src → build/src}/js/components/prism/PrismLiveModal.d.ts +0 -0
  133. /package/{src → build/src}/js/components/prism/PrismLiveModal.d.ts.map +0 -0
  134. /package/{src → build/src}/js/components/prism/PrismPlayerControls.d.ts +0 -0
  135. /package/{src → build/src}/js/components/prism/PrismPlayerControls.d.ts.map +0 -0
  136. /package/{src → build/src}/js/components/prism/PrismPlayerPreview.d.ts +0 -0
  137. /package/{src → build/src}/js/components/prism/PrismPlayerPreview.d.ts.map +0 -0
  138. /package/{src → build/src}/js/components/prism/index.d.ts +0 -0
  139. /package/{src → build/src}/js/components/prism/index.d.ts.map +0 -0
  140. /package/{src → build/src}/js/constants/baseUrls.d.ts +0 -0
  141. /package/{src → build/src}/js/constants/baseUrls.d.ts.map +0 -0
  142. /package/{src → build/src}/js/constants/embed.d.ts +0 -0
  143. /package/{src → build/src}/js/constants/embed.d.ts.map +0 -0
  144. /package/{src → build/src}/js/constants/player.d.ts +0 -0
  145. /package/{src → build/src}/js/constants/player.d.ts.map +0 -0
  146. /package/{src → build/src}/js/constants/pluginsRegistry.d.ts +0 -0
  147. /package/{src → build/src}/js/constants/pluginsRegistry.d.ts.map +0 -0
  148. /package/{src → build/src}/js/hooks/useGetPlayers.d.ts +0 -0
  149. /package/{src → build/src}/js/hooks/useGetPlayers.d.ts.map +0 -0
  150. /package/{src → build/src}/js/i18n.d.ts +0 -0
  151. /package/{src → build/src}/js/i18n.d.ts.map +0 -0
  152. /package/{src → build/src}/js/services/players-api.d.ts +0 -0
  153. /package/{src → build/src}/js/services/players-api.d.ts.map +0 -0
  154. /package/{src → build/src}/js/services/url-shortener.d.ts +0 -0
  155. /package/{src → build/src}/js/services/url-shortener.d.ts.map +0 -0
  156. /package/{src → build/src}/js/types/EmbedCodeModalProps.d.ts +0 -0
  157. /package/{src → build/src}/js/types/EmbedCodeModalProps.d.ts.map +0 -0
  158. /package/{src → build/src}/js/types/EmbedCodeProps.d.ts +0 -0
  159. /package/{src → build/src}/js/types/EmbedCodeProps.d.ts.map +0 -0
  160. /package/{src → build/src}/js/types/players.d.ts +0 -0
  161. /package/{src → build/src}/js/types/players.d.ts.map +0 -0
  162. /package/{src → build/src}/js/types/studioModule.d.ts +0 -0
  163. /package/{src → build/src}/js/types/studioModule.d.ts.map +0 -0
  164. /package/{src → build/src}/js/utils/addPluginsDataToPlayers.d.ts +0 -0
  165. /package/{src → build/src}/js/utils/addPluginsDataToPlayers.d.ts.map +0 -0
  166. /package/{src → build/src}/js/utils/call.d.ts +0 -0
  167. /package/{src → build/src}/js/utils/call.d.ts.map +0 -0
  168. /package/{src → build/src}/js/utils/dimensions.d.ts +0 -0
  169. /package/{src → build/src}/js/utils/dimensions.d.ts.map +0 -0
  170. /package/{src → build/src}/js/utils/embedCodeGenerator.d.ts +0 -0
  171. /package/{src → build/src}/js/utils/embedCodeGenerator.d.ts.map +0 -0
  172. /package/{src → build/src}/js/utils/featureSwitches.d.ts +0 -0
  173. /package/{src → build/src}/js/utils/featureSwitches.d.ts.map +0 -0
  174. /package/{src → build/src}/js/utils/util.d.ts +0 -0
  175. /package/{src → build/src}/js/utils/util.d.ts.map +0 -0
  176. /package/{src → build/src}/module.d.ts +0 -0
  177. /package/{src → build/src}/module.d.ts.map +0 -0
  178. /package/{src → build/src}/styles/typings.td.d.ts +0 -0
  179. /package/{src → build/src}/styles/typings.td.d.ts.map +0 -0
  180. /package/{src → build/src}/types/studio.d.ts +0 -0
  181. /package/{src → build/src}/types/studio.d.ts.map +0 -0
@@ -0,0 +1,343 @@
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
+ import {
3
+ PrismDropDown,
4
+ PrismInput,
5
+ PrismIconButton,
6
+ PrismCheckbox,
7
+ PrismCodeCard,
8
+ } from '@brightcove/prism-core';
9
+ import { useTranslation } from 'react-i18next';
10
+
11
+ import { PlayerSelectItem, PlayersObject } from 'js/types/players';
12
+ import {
13
+ LivePayload,
14
+ EmbedCodeAspectRatio,
15
+ EmbedCodeProperties,
16
+ } from 'js/types/EmbedCodeModalProps';
17
+ import {
18
+ calculateDimensions,
19
+ calculateHeight,
20
+ shouldUpdateWidth,
21
+ } from 'js/utils/dimensions';
22
+ import { generateEmbedCode } from 'js/utils/embedCodeGenerator';
23
+
24
+ import css from 'styles/prism/PrismLiveModal.sass';
25
+
26
+ interface PrismPlayerControlsProps {
27
+ video: LivePayload;
28
+ players: PlayersObject;
29
+ playerListForSelection: PlayerSelectItem[];
30
+ selectedPlayer: string;
31
+ onPlayerChange: (playerId: string) => void;
32
+ embedProperties: EmbedCodeProperties;
33
+ setEmbedProperties: (value: EmbedCodeProperties) => void;
34
+ showDVR?: boolean;
35
+ onDVRClick?: (newValue: boolean) => void;
36
+ dvrTime?: number;
37
+ showLowLatency?: boolean;
38
+ onLowLatencyClick?: (newValue: boolean) => void;
39
+ adConfigValue?: string;
40
+ onAdConfigChange?: (value: string) => void;
41
+ ssai?: boolean;
42
+ adConfigOptions?: Array<{ label: string; value: string }>;
43
+ }
44
+
45
+ export const PrismPlayerControls = ({
46
+ video,
47
+ players,
48
+ playerListForSelection,
49
+ selectedPlayer,
50
+ onPlayerChange,
51
+ embedProperties,
52
+ setEmbedProperties,
53
+ showDVR,
54
+ onDVRClick,
55
+ dvrTime,
56
+ showLowLatency,
57
+ onLowLatencyClick,
58
+ adConfigValue,
59
+ onAdConfigChange,
60
+ ssai,
61
+ adConfigOptions = [],
62
+ }: PrismPlayerControlsProps) => {
63
+ const { t } = useTranslation();
64
+ const [prevAspectRatio, setPrevAspectRatio] = useState(
65
+ embedProperties.aspectRatio,
66
+ );
67
+ const [aspectRatioLocked, setAspectRatioLocked] = useState(
68
+ embedProperties.aspectRatio !== EmbedCodeAspectRatio.CUSTOM,
69
+ );
70
+ const [dvrEnabled, setDvrEnabled] = useState(!showLowLatency);
71
+ const [lowLatencyEnabled, setLowLatencyEnabled] = useState(true);
72
+
73
+ const handlePropertiesUpdate = useCallback(
74
+ (name: string, value: string | boolean | number) => {
75
+ if (name === 'aspectRatio') {
76
+ const calculateNewWidth = shouldUpdateWidth(
77
+ prevAspectRatio,
78
+ value as EmbedCodeAspectRatio,
79
+ );
80
+
81
+ setPrevAspectRatio(value as EmbedCodeAspectRatio);
82
+ setAspectRatioLocked(value !== EmbedCodeAspectRatio.CUSTOM);
83
+
84
+ const dimensions = calculateDimensions(
85
+ calculateNewWidth,
86
+ value as EmbedCodeAspectRatio,
87
+ embedProperties.width,
88
+ embedProperties.height,
89
+ );
90
+
91
+ setEmbedProperties({
92
+ ...embedProperties,
93
+ aspectRatio: value as EmbedCodeAspectRatio,
94
+ width: dimensions.width as number,
95
+ height: dimensions.height,
96
+ });
97
+
98
+ return;
99
+ } else if (name === 'width' && aspectRatioLocked) {
100
+ const height = calculateHeight(
101
+ value as number,
102
+ embedProperties.aspectRatio,
103
+ );
104
+
105
+ setEmbedProperties({
106
+ ...embedProperties,
107
+ width: value as number,
108
+ height,
109
+ });
110
+
111
+ return;
112
+ }
113
+
114
+ setEmbedProperties({
115
+ ...embedProperties,
116
+ [name]: value,
117
+ });
118
+ },
119
+ [prevAspectRatio, aspectRatioLocked, embedProperties, setEmbedProperties],
120
+ );
121
+
122
+ const toggleAspectRatioLock = useCallback(() => {
123
+ if (embedProperties.aspectRatio === EmbedCodeAspectRatio.CUSTOM) {
124
+ return;
125
+ }
126
+ setAspectRatioLocked(!aspectRatioLocked);
127
+ }, [aspectRatioLocked, embedProperties.aspectRatio]);
128
+
129
+ // Player dropdown items
130
+ const playerItems = playerListForSelection.map((player) => ({
131
+ value: player.value,
132
+ label: player.label,
133
+ }));
134
+
135
+ // Sizing dropdown items
136
+ const sizingItems = [
137
+ { value: 'responsive', label: 'Responsive' },
138
+ { value: 'fixed', label: 'Fixed' },
139
+ ];
140
+
141
+ // Aspect ratio dropdown items
142
+ const aspectRatioItems = [
143
+ { value: EmbedCodeAspectRatio.SIXTEEN_NINE, label: '16:9 (Desktop)' },
144
+ { value: EmbedCodeAspectRatio.NINE_SIXTEEN, label: '9:16 (Mobile)' },
145
+ { value: EmbedCodeAspectRatio.ONE_ONE, label: '1:1' },
146
+ { value: EmbedCodeAspectRatio.FOUR_THREE, label: '4:3' },
147
+ { value: EmbedCodeAspectRatio.CUSTOM, label: 'Custom' },
148
+ ];
149
+
150
+ // Generate embed code
151
+ const player = players[selectedPlayer];
152
+ const { embedCode, embedCodeJS } = generateEmbedCode({
153
+ accountId: video.accountId,
154
+ playerId: selectedPlayer,
155
+ videoId: video.videoId,
156
+ embedProperties,
157
+ isCampaign: player?.hasCampaignPlugin || false,
158
+ isViewerIdSource: player?.hasViewerIDSource || false,
159
+ isBcMapTracking: player?.hasBcMapTracking || false,
160
+ livePlaybackToken: video.livePlaybackToken,
161
+ adConfigId: adConfigValue || video.adConfigId,
162
+ });
163
+
164
+ const codeCardTabs = useMemo(
165
+ () => [
166
+ {
167
+ label: (t('tabs.labels.iframe') as string) || 'iframe',
168
+ code: embedCode,
169
+ language: 'html',
170
+ },
171
+ {
172
+ label: (t('tabs.labels.javascript') as string) || 'JavaScript',
173
+ code: embedCodeJS,
174
+ language: 'javascript',
175
+ },
176
+ ],
177
+ [embedCode, embedCodeJS, t],
178
+ );
179
+
180
+ // Warning message for custom plugins
181
+ const warningMessage: string | undefined =
182
+ player?.hasCampaignPlugin || player?.hasViewerIDSource
183
+ ? (t('customPluginNotice') as string)
184
+ : void 0;
185
+
186
+ const handleDVRChange = useCallback(
187
+ (checked: boolean) => {
188
+ setDvrEnabled(checked);
189
+ if (onDVRClick) {
190
+ onDVRClick(checked);
191
+ }
192
+ if (checked && lowLatencyEnabled) {
193
+ setLowLatencyEnabled(false);
194
+ if (onLowLatencyClick) {
195
+ onLowLatencyClick(false);
196
+ }
197
+ }
198
+ },
199
+ [lowLatencyEnabled, onDVRClick, onLowLatencyClick],
200
+ );
201
+
202
+ const handleLowLatencyChange = useCallback(
203
+ (checked: boolean) => {
204
+ setLowLatencyEnabled(checked);
205
+ if (onLowLatencyClick) {
206
+ onLowLatencyClick(checked);
207
+ }
208
+ if (checked && dvrEnabled) {
209
+ setDvrEnabled(false);
210
+ if (onDVRClick) {
211
+ onDVRClick(false);
212
+ }
213
+ }
214
+ },
215
+ [dvrEnabled, onDVRClick, onLowLatencyClick],
216
+ );
217
+
218
+ return (
219
+ <div className={css.prismLeftColumn}>
220
+ {/* Player Dropdown */}
221
+ <PrismDropDown
222
+ label={t('player') as string}
223
+ items={playerItems}
224
+ value={selectedPlayer}
225
+ onChange={(item) => onPlayerChange(item.value as string)}
226
+ searchable
227
+ placeholder='Select Player'
228
+ />
229
+
230
+ {/* Player Sizing Dropdown */}
231
+ <PrismDropDown
232
+ label={(t('prism.customSettings.sizing') as string) || 'Player Sizing'}
233
+ items={sizingItems}
234
+ value={embedProperties.responsive ? 'responsive' : 'fixed'}
235
+ onChange={(item) =>
236
+ handlePropertiesUpdate('responsive', item.value === 'responsive')
237
+ }
238
+ />
239
+
240
+ {/* Aspect Ratio Dropdown */}
241
+ <PrismDropDown
242
+ label={(t('customSettings.aspectRatio') as string) || 'Aspect Ratio'}
243
+ items={aspectRatioItems}
244
+ value={embedProperties.aspectRatio}
245
+ onChange={(item) => handlePropertiesUpdate('aspectRatio', item.value)}
246
+ />
247
+
248
+ {/* Width/Height Input Group */}
249
+ <div className={css.prismInputGroup}>
250
+ <PrismInput
251
+ variant='FieldGroup'
252
+ label={(t('customSettings.playerWidth') as string) || 'Player Width'}
253
+ type='number'
254
+ value={String(embedProperties.width)}
255
+ onChange={(value) => handlePropertiesUpdate('width', Number(value))}
256
+ groupTitle='Unit'
257
+ groupValue={embedProperties.units}
258
+ onGroupChange={(unit) => handlePropertiesUpdate('units', unit)}
259
+ groupOptions={['px', '%']}
260
+ />
261
+
262
+ <PrismIconButton
263
+ icon='link'
264
+ variant='Ghost'
265
+ size='small'
266
+ onClick={toggleAspectRatioLock}
267
+ disabled={embedProperties.aspectRatio === EmbedCodeAspectRatio.CUSTOM}
268
+ className={css.prismLinkButton}
269
+ />
270
+
271
+ <PrismInput
272
+ variant='FieldGroup'
273
+ label={
274
+ (t('customSettings.playerHeight') as string) || 'Player Height'
275
+ }
276
+ type='number'
277
+ value={String(embedProperties.height)}
278
+ onChange={(value) => handlePropertiesUpdate('height', Number(value))}
279
+ disabled={aspectRatioLocked}
280
+ groupTitle='Unit'
281
+ groupValue={embedProperties.units}
282
+ onGroupChange={(unit) => handlePropertiesUpdate('units', unit)}
283
+ groupOptions={['px', '%']}
284
+ />
285
+ </div>
286
+
287
+ {/* DVR Checkbox (conditional) */}
288
+ {showDVR && (
289
+ <div>
290
+ <PrismCheckbox
291
+ label={(t('dvr.allowDVR') as string) || 'Enable DVR'}
292
+ checked={dvrEnabled}
293
+ onChange={(e) => handleDVRChange(e.target.checked)}
294
+ disabled={showLowLatency && lowLatencyEnabled}
295
+ />
296
+ {dvrEnabled && typeof dvrTime === 'number' && (
297
+ <div className={css.prismInputGroup}>
298
+ <PrismInput
299
+ variant='FieldGroup'
300
+ label={(t('dvr.inputLabel') as string) || 'DVR Window'}
301
+ type='number'
302
+ value={String(dvrTime)}
303
+ disabled
304
+ groupTitle='Unit'
305
+ groupValue={(t('dvr.unit') as string) || 'sec'}
306
+ groupOptions={[(t('dvr.unit') as string) || 'sec']}
307
+ />
308
+ </div>
309
+ )}
310
+ </div>
311
+ )}
312
+
313
+ {/* Low Latency Checkbox (conditional) */}
314
+ {showLowLatency && (
315
+ <PrismCheckbox
316
+ label={(t('lowLatency') as string) || 'Enable Low Latency'}
317
+ checked={lowLatencyEnabled}
318
+ onChange={(e) => handleLowLatencyChange(e.target.checked)}
319
+ disabled={showDVR && dvrEnabled}
320
+ />
321
+ )}
322
+
323
+ {/* Ad Config Dropdown (conditional) */}
324
+ {ssai && adConfigOptions.length > 0 && onAdConfigChange && (
325
+ <PrismDropDown
326
+ label={(t('adConfiguration') as string) || 'Ad Configuration'}
327
+ items={adConfigOptions.map((opt) => ({
328
+ value: opt.value,
329
+ label: opt.label,
330
+ }))}
331
+ value={adConfigValue || adConfigOptions[0].value}
332
+ onChange={(item) => onAdConfigChange(item.value as string)}
333
+ />
334
+ )}
335
+
336
+ {warningMessage && (
337
+ <div className={css.prismCustomPluginNotice}>{warningMessage}</div>
338
+ )}
339
+
340
+ <PrismCodeCard className={css.prismCodeCard} tabs={codeCardTabs} />
341
+ </div>
342
+ );
343
+ };
@@ -0,0 +1,98 @@
1
+ import React, { useCallback } from 'react';
2
+ import ReactPlayerLoader from '@brightcove/react-player-loader';
3
+ import { PrismButton } from '@brightcove/prism-core';
4
+ import { getBrightcove, getUrlBase } from 'js/utils/util';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import css from 'styles/prism/PrismLiveModal.sass';
8
+
9
+ interface PrismPlayerPreviewProps {
10
+ accountId: string;
11
+ playerId: string;
12
+ videoId: string;
13
+ previewUrl: string;
14
+ livePlaybackToken?: string;
15
+ adConfigId?: string;
16
+ }
17
+
18
+ export const PrismPlayerPreview = ({
19
+ accountId,
20
+ playerId,
21
+ videoId,
22
+ previewUrl,
23
+ }: PrismPlayerPreviewProps) => {
24
+ const { t } = useTranslation();
25
+
26
+ const handleCopyUrl = useCallback(() => {
27
+ if (!previewUrl) {
28
+ getBrightcove().Toaster.error({
29
+ title: t('toasterTitle') as string,
30
+ message: 'Please select a player first',
31
+ });
32
+ return;
33
+ }
34
+ navigator.clipboard.writeText(previewUrl).then(() => {
35
+ getBrightcove().Toaster.success({
36
+ title: t('toasterTitle') as string,
37
+ message: t('toasterGetURLSuccess') as string,
38
+ });
39
+ });
40
+ }, [previewUrl, t]);
41
+
42
+ const handleOpenPreview = useCallback(() => {
43
+ if (!previewUrl) {
44
+ getBrightcove().Toaster.error({
45
+ title: t('toasterTitle') as string,
46
+ message: 'Please select a player first',
47
+ });
48
+ return;
49
+ }
50
+ window.open(previewUrl, '_blank', 'noopener,noreferrer');
51
+ }, [previewUrl, t]);
52
+
53
+ // Build player options for Live modal
54
+ // Note: Following Bluesteel pattern - we DON'T pass adConfigId or livePlaybackToken
55
+ // to ReactPlayerLoader. These are only used in the previewUrl for external links
56
+ // and in the embed code generation, not for the embedded player preview.
57
+ const playerOptions: Record<string, string> = {
58
+ accountId,
59
+ playerId,
60
+ videoId,
61
+ };
62
+
63
+ return (
64
+ <div className={css.prismPlayerPreviewContainer}>
65
+ <div className={css.prismPlayerActions}>
66
+ <PrismButton
67
+ variant='Primary'
68
+ size='small'
69
+ onClick={handleCopyUrl}
70
+ rightIcon='copy'
71
+ disabled={!previewUrl}
72
+ >
73
+ {t('prism.getURL') as string}
74
+ </PrismButton>
75
+ <PrismButton
76
+ variant='Primary'
77
+ size='small'
78
+ onClick={handleOpenPreview}
79
+ rightIcon='link'
80
+ disabled={!previewUrl}
81
+ >
82
+ {t('previewButton') as string}
83
+ </PrismButton>
84
+ </div>
85
+
86
+ <div className={css.prismPlayerWrapper}>
87
+ <ReactPlayerLoader
88
+ key={`${accountId}-${playerId}-${videoId}`}
89
+ {...playerOptions}
90
+ baseUrl={getUrlBase()}
91
+ attrs={{ className: css.playerLoader }}
92
+ embedType='iframe'
93
+ refNode='player-container'
94
+ />
95
+ </div>
96
+ </div>
97
+ );
98
+ };
@@ -0,0 +1,3 @@
1
+ export { PrismLiveModal } from './PrismLiveModal';
2
+ export { PrismPlayerControls } from './PrismPlayerControls';
3
+ export { PrismPlayerPreview } from './PrismPlayerPreview';
@@ -0,0 +1,4 @@
1
+ export const QA_URL = 'https://players.qa.brightcove.net/';
2
+ export const PROD_URL = 'https://players.brightcove.net/';
3
+ export const QA_PREVIEW_URL = 'https://preview-players.qa.brightcove.net';
4
+ export const PROD_PREVIEW_URL = 'https://preview-players.brightcove.net';
@@ -0,0 +1,12 @@
1
+ import {
2
+ EmbedCodeAspectRatio,
3
+ EmbedCodeUnits,
4
+ } from 'js/types/EmbedCodeModalProps';
5
+
6
+ export const EMBED_DEFAULT = {
7
+ responsive: false,
8
+ width: 960,
9
+ height: 540,
10
+ units: EmbedCodeUnits.PX,
11
+ aspectRatio: EmbedCodeAspectRatio.CUSTOM,
12
+ };
@@ -0,0 +1,32 @@
1
+ export const DEFAULT_PLAYER_ID = 'default';
2
+ export const DEFAULT_PLAYER_NAME = 'Brightcove Default Player';
3
+
4
+ export const PLAYER_LIST_FOR_SELECTION = [
5
+ {
6
+ label: DEFAULT_PLAYER_NAME,
7
+ value: DEFAULT_PLAYER_ID,
8
+ },
9
+ ];
10
+
11
+ export const FAVORITE_PLAYER_SETTING = 'favorite_player_id';
12
+
13
+ export const PLAYERS = {
14
+ default: {
15
+ hasCustomPlugins: false,
16
+ id: DEFAULT_PLAYER_ID,
17
+ hasCampaignPlugin: false,
18
+ label: DEFAULT_PLAYER_NAME,
19
+ hasInteractivityPlugin: false,
20
+ hasViewerIDSource: false,
21
+ hasBcMapTracking: false,
22
+ },
23
+ };
24
+
25
+ export const DEFAULT_INTERACTIVITY_PLAYER = {
26
+ name: 'Interactivity Player',
27
+ registry_id: '@brightcove/videojs-interactivity-viewer',
28
+ version: '2.x',
29
+ options: {
30
+ mobilefullscreen: true,
31
+ },
32
+ };
@@ -0,0 +1,30 @@
1
+ // Plugins available in the BC registry
2
+ export const bcHostedPlugins = [
3
+ 'bcAa',
4
+ 'bcAirplay',
5
+ 'bcGa',
6
+ 'bcGtm',
7
+ 'bcPlaylistUi',
8
+ 'bcTealium',
9
+ 'bcMapPlugin',
10
+ 'chromecastReceiver',
11
+ 'customEndscreen',
12
+ 'eme',
13
+ 'ima3',
14
+ 'interactivityViewer',
15
+ 'kollective',
16
+ 'overlay',
17
+ 'pip',
18
+ 'playlistEndscreen',
19
+ 'qualityMenu',
20
+ 'social',
21
+ 'ssai',
22
+ 'thumbnails',
23
+ ];
24
+
25
+ export const CAMPAIGN_PLUGIN_NAME = 'bcMapPlugin';
26
+ export const VIEWER_ID_KEY_NAME = 'viewer_id_source';
27
+ export const BC_MAP_TRACKING_NAME = 'bcMapTracking';
28
+ export const INTERACTIVITY_PLUGIN_NAME = 'interactivityViewer';
29
+ export const INTERACTIVITY_REGISTRY_ID =
30
+ '@brightcove/videojs-interactivity-viewer';
@@ -0,0 +1,12 @@
1
+ import { useQuery } from 'react-query';
2
+
3
+ import { getPlayers } from 'js/services/players-api';
4
+
5
+ const useGetPlayers = (isPlayersCallEnabled: boolean) => {
6
+ return useQuery(['players'], () => getPlayers(), {
7
+ refetchOnWindowFocus: false,
8
+ enabled: isPlayersCallEnabled,
9
+ });
10
+ };
11
+
12
+ export default useGetPlayers;
package/src/js/i18n.ts ADDED
@@ -0,0 +1,43 @@
1
+ import i18n from 'i18next';
2
+ import { initReactI18next } from 'react-i18next';
3
+
4
+ import common_en from 'translations/en.json';
5
+ import common_es from 'translations/es.json';
6
+ import common_fr from 'translations/fr.json';
7
+ import common_ja from 'translations/ja.json';
8
+ import common_ko from 'translations/ko.json';
9
+ import common_zh from 'translations/zh.json';
10
+
11
+ export const resources = {
12
+ en: {
13
+ translation: common_en,
14
+ },
15
+ es: {
16
+ translation: common_es,
17
+ },
18
+ fr: {
19
+ translation: common_fr,
20
+ },
21
+ ja: {
22
+ translation: common_ja,
23
+ },
24
+ ko: {
25
+ translation: common_ko,
26
+ },
27
+ zh: {
28
+ translation: common_zh,
29
+ },
30
+ };
31
+
32
+ i18n.use(initReactI18next).init({
33
+ lng: window.StudioModule?.config?.lang || 'en',
34
+ fallbackLng: 'en',
35
+ debug: true,
36
+ returnObjects: true,
37
+ resources,
38
+ interpolation: {
39
+ escapeValue: false, // react already safe from xss
40
+ },
41
+ });
42
+
43
+ export default i18n;
@@ -0,0 +1,26 @@
1
+ import {
2
+ getAllPlayers,
3
+ createPlayer as createPlayerAPI,
4
+ getPlayer as getPlayerAPI,
5
+ } from '@brightcove/studio-api-services/api/players-api';
6
+ import { callAPI } from 'js/utils/call';
7
+ import {
8
+ GetPlayersPayload,
9
+ CreatePlayerPayload,
10
+ CreatePlayerResponse,
11
+ PlayerItem,
12
+ } from 'js/types/players';
13
+
14
+ export const getPlayers = async (): Promise<GetPlayersPayload> => {
15
+ return callAPI(getAllPlayers, {});
16
+ };
17
+
18
+ export const createPlayer = async (
19
+ payload: CreatePlayerPayload,
20
+ ): Promise<CreatePlayerResponse> => {
21
+ return callAPI(createPlayerAPI, payload);
22
+ };
23
+
24
+ export const getPlayer = async (playerId: string): Promise<PlayerItem> => {
25
+ return callAPI(getPlayerAPI, { playerId });
26
+ };
@@ -0,0 +1,10 @@
1
+ import { getShortenedUrl } from '@brightcove/studio-api-services/api/url-shortener-api';
2
+ import { callAPI } from 'js/utils/call';
3
+
4
+ export const shortenUrl = async ({
5
+ longUrl,
6
+ }: {
7
+ longUrl: string;
8
+ }): Promise<string> => {
9
+ return callAPI(getShortenedUrl, { longUrl });
10
+ };
@@ -0,0 +1,72 @@
1
+ // export payload that will be used to pass data into component
2
+ export interface VideoPayload {
3
+ videoId: string;
4
+ videoName: string;
5
+ accountId: string;
6
+ posterUrl?: string;
7
+ duration: number;
8
+ }
9
+
10
+ export interface PlayerPayload {
11
+ id: string;
12
+ configuration?: Record<string, unknown>;
13
+ name?: string;
14
+ accountId?: string;
15
+ embedCodeProperties?: EmbedCodeProperties;
16
+ isOutstreamPlayer?: boolean;
17
+ }
18
+
19
+ export interface ProjectPayload {
20
+ accountId: string;
21
+ videoId: string;
22
+ interactivityProjectId: string;
23
+ interactivityProjectName: string;
24
+ }
25
+
26
+ export interface LivePayload {
27
+ videoId: string;
28
+ accountId: string;
29
+ livePlaybackToken: string;
30
+ adConfigId: string;
31
+ }
32
+
33
+ export enum EmbedCodeModalTypes {
34
+ PLAYER = 'player',
35
+ VIDEO = 'video',
36
+ PROJECT = 'project',
37
+ LIVE = 'live',
38
+ }
39
+
40
+ export enum NoInteractivityPlayerModalTypes {
41
+ NO_PLAYER = 'no-player',
42
+ NO_PERMISSION = 'no-permission',
43
+ }
44
+
45
+ export type ModalDataPayload =
46
+ | VideoPayload
47
+ | PlayerPayload
48
+ | ProjectPayload
49
+ | LivePayload;
50
+
51
+ export enum EmbedCodeUnits {
52
+ PX = 'px',
53
+ EM = 'em',
54
+ PERCENT = '%',
55
+ }
56
+
57
+ export enum EmbedCodeAspectRatio {
58
+ FLUID = 'fluid',
59
+ CUSTOM = 'custom',
60
+ ONE_ONE = '1:1',
61
+ FOUR_THREE = '4:3',
62
+ SIXTEEN_NINE = '16:9',
63
+ NINE_SIXTEEN = '9:16',
64
+ }
65
+
66
+ export interface EmbedCodeProperties {
67
+ width: number;
68
+ height: number;
69
+ units: EmbedCodeUnits;
70
+ responsive: boolean;
71
+ aspectRatio: EmbedCodeAspectRatio;
72
+ }