@brightcove/components-embed-code-modal 1.1.2 → 1.1.3

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 (196) 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 +423 -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__/PrismLiveModal.test.tsx +85 -0
  17. package/__tests__/PrismPlayerControls.test.tsx +139 -0
  18. package/__tests__/PrismPlayerPreview.test.tsx +168 -0
  19. package/__tests__/TabsHeader.test.tsx +19 -0
  20. package/__tests__/TabsSection.test.tsx +46 -0
  21. package/__tests__/TitleWithTooltip.test.tsx +45 -0
  22. package/__tests__/VideoPlayer.test.tsx +105 -0
  23. package/__tests__/embedCodeGenerator.test.ts +111 -0
  24. package/__tests__/util.test.ts +89 -0
  25. package/babel.config.json +17 -0
  26. package/build/README.md +109 -0
  27. package/build/__tests__/PrismLiveModal.test.d.ts +2 -0
  28. package/build/__tests__/PrismLiveModal.test.d.ts.map +1 -0
  29. package/build/index.js +9 -0
  30. package/build/index.js.map +1 -0
  31. package/build/package.json +100 -0
  32. package/{src → build/src}/js/components/prism/PrismLiveModal.d.ts.map +1 -1
  33. package/{src → build/src}/js/components/prism/PrismPlayerControls.d.ts +2 -1
  34. package/{src → build/src}/js/components/prism/PrismPlayerControls.d.ts.map +1 -1
  35. package/{src → build/src}/js/components/prism/PrismPlayerPreview.d.ts +2 -1
  36. package/build/src/js/components/prism/PrismPlayerPreview.d.ts.map +1 -0
  37. package/build/src/js/hooks/useIsMobile.d.ts +3 -0
  38. package/build/src/js/hooks/useIsMobile.d.ts.map +1 -0
  39. package/{src → build/src}/js/i18n.d.ts +24 -0
  40. package/{src → build/src}/js/i18n.d.ts.map +1 -1
  41. package/etc/wildcard.brightcove.com.key +27 -0
  42. package/etc/wildcard.brightcove.com.pem +96 -0
  43. package/jest/jest.setup.js +90 -0
  44. package/jest/jest.stub.js +4 -0
  45. package/jest.config.js +24 -0
  46. package/package.json +17 -7
  47. package/scripts/create-dist-pkg-prod.js +19 -0
  48. package/src/index.ts +17 -0
  49. package/src/js/EmbedCodeModal.tsx +105 -0
  50. package/src/js/components/CustomSettingsDropdown.tsx +184 -0
  51. package/src/js/components/EmbedCopySection.tsx +130 -0
  52. package/src/js/components/ModalContainer.tsx +367 -0
  53. package/src/js/components/NoPlayerModal.tsx +42 -0
  54. package/src/js/components/PlayerPreview.tsx +116 -0
  55. package/src/js/components/TabsHeader.tsx +31 -0
  56. package/src/js/components/TabsSection.tsx +74 -0
  57. package/src/js/components/TitleWithTooltip.tsx +39 -0
  58. package/src/js/components/VideoLinks.tsx +83 -0
  59. package/src/js/components/VideoPlayer.tsx +324 -0
  60. package/src/js/components/index.ts +9 -0
  61. package/src/js/components/prism/PrismLiveModal.tsx +196 -0
  62. package/src/js/components/prism/PrismPlayerControls.tsx +356 -0
  63. package/src/js/components/prism/PrismPlayerPreview.tsx +200 -0
  64. package/src/js/components/prism/index.ts +3 -0
  65. package/src/js/constants/baseUrls.ts +4 -0
  66. package/src/js/constants/embed.ts +12 -0
  67. package/src/js/constants/player.ts +32 -0
  68. package/src/js/constants/pluginsRegistry.ts +30 -0
  69. package/src/js/hooks/useGetPlayers.ts +12 -0
  70. package/src/js/hooks/useIsMobile.ts +33 -0
  71. package/src/js/i18n.ts +43 -0
  72. package/src/js/services/players-api.ts +26 -0
  73. package/src/js/services/url-shortener.ts +10 -0
  74. package/src/js/types/EmbedCodeModalProps.ts +72 -0
  75. package/src/js/types/EmbedCodeProps.ts +13 -0
  76. package/src/js/types/brightcoveEmbedCode.d.ts +18 -0
  77. package/src/js/types/players.ts +64 -0
  78. package/src/js/types/react-i18next.d.ts +8 -0
  79. package/src/js/types/studioModule.ts +7 -0
  80. package/src/js/utils/addPluginsDataToPlayers.ts +59 -0
  81. package/src/js/utils/call.ts +31 -0
  82. package/src/js/utils/dimensions.ts +53 -0
  83. package/src/js/utils/embedCodeGenerator.ts +120 -0
  84. package/src/js/utils/featureSwitches.ts +16 -0
  85. package/src/js/utils/util.ts +153 -0
  86. package/src/module.tsx +280 -0
  87. package/src/styles/CustomSettingsDropdown.sass +42 -0
  88. package/src/styles/EmbedCode.sass +30 -0
  89. package/src/styles/EmbedCodeModal.sass +76 -0
  90. package/src/styles/base.sass +1 -0
  91. package/src/styles/prism/PrismLiveModal.sass +128 -0
  92. package/src/styles/theme.sass +11 -0
  93. package/src/styles/typings.td.ts +9 -0
  94. package/src/translations/en.json +69 -0
  95. package/src/translations/es.json +69 -0
  96. package/src/translations/fr.json +69 -0
  97. package/src/translations/ja.json +69 -0
  98. package/src/translations/ko.json +69 -0
  99. package/src/translations/zh.json +69 -0
  100. package/src/types/studio.ts +197 -0
  101. package/tsconfig.json +34 -0
  102. package/webpack.config.js +117 -0
  103. package/index.js +0 -9
  104. package/index.js.map +0 -1
  105. package/src/js/components/prism/PrismPlayerPreview.d.ts.map +0 -1
  106. /package/{__tests__ → build/__tests__}/EmbedCode.test.d.ts +0 -0
  107. /package/{__tests__ → build/__tests__}/EmbedCode.test.d.ts.map +0 -0
  108. /package/{__tests__ → build/__tests__}/InteractivityProject.test.d.ts +0 -0
  109. /package/{__tests__ → build/__tests__}/InteractivityProject.test.d.ts.map +0 -0
  110. /package/{__tests__ → build/__tests__}/PlayerPreview.test.d.ts +0 -0
  111. /package/{__tests__ → build/__tests__}/PlayerPreview.test.d.ts.map +0 -0
  112. /package/{__tests__ → build/__tests__}/PrismPlayerControls.test.d.ts +0 -0
  113. /package/{__tests__ → build/__tests__}/PrismPlayerControls.test.d.ts.map +0 -0
  114. /package/{__tests__ → build/__tests__}/PrismPlayerPreview.test.d.ts +0 -0
  115. /package/{__tests__ → build/__tests__}/PrismPlayerPreview.test.d.ts.map +0 -0
  116. /package/{__tests__ → build/__tests__}/TabsHeader.test.d.ts +0 -0
  117. /package/{__tests__ → build/__tests__}/TabsHeader.test.d.ts.map +0 -0
  118. /package/{__tests__ → build/__tests__}/TabsSection.test.d.ts +0 -0
  119. /package/{__tests__ → build/__tests__}/TabsSection.test.d.ts.map +0 -0
  120. /package/{__tests__ → build/__tests__}/TitleWithTooltip.test.d.ts +0 -0
  121. /package/{__tests__ → build/__tests__}/TitleWithTooltip.test.d.ts.map +0 -0
  122. /package/{__tests__ → build/__tests__}/VideoPlayer.test.d.ts +0 -0
  123. /package/{__tests__ → build/__tests__}/VideoPlayer.test.d.ts.map +0 -0
  124. /package/{__tests__ → build/__tests__}/embedCodeGenerator.test.d.ts +0 -0
  125. /package/{__tests__ → build/__tests__}/embedCodeGenerator.test.d.ts.map +0 -0
  126. /package/{__tests__ → build/__tests__}/util.test.d.ts +0 -0
  127. /package/{__tests__ → build/__tests__}/util.test.d.ts.map +0 -0
  128. /package/{src → build/src}/index.d.ts +0 -0
  129. /package/{src → build/src}/index.d.ts.map +0 -0
  130. /package/{src → build/src}/js/EmbedCodeModal.d.ts +0 -0
  131. /package/{src → build/src}/js/EmbedCodeModal.d.ts.map +0 -0
  132. /package/{src → build/src}/js/components/CustomSettingsDropdown.d.ts +0 -0
  133. /package/{src → build/src}/js/components/CustomSettingsDropdown.d.ts.map +0 -0
  134. /package/{src → build/src}/js/components/EmbedCopySection.d.ts +0 -0
  135. /package/{src → build/src}/js/components/EmbedCopySection.d.ts.map +0 -0
  136. /package/{src → build/src}/js/components/ModalContainer.d.ts +0 -0
  137. /package/{src → build/src}/js/components/ModalContainer.d.ts.map +0 -0
  138. /package/{src → build/src}/js/components/NoPlayerModal.d.ts +0 -0
  139. /package/{src → build/src}/js/components/NoPlayerModal.d.ts.map +0 -0
  140. /package/{src → build/src}/js/components/PlayerPreview.d.ts +0 -0
  141. /package/{src → build/src}/js/components/PlayerPreview.d.ts.map +0 -0
  142. /package/{src → build/src}/js/components/TabsHeader.d.ts +0 -0
  143. /package/{src → build/src}/js/components/TabsHeader.d.ts.map +0 -0
  144. /package/{src → build/src}/js/components/TabsSection.d.ts +0 -0
  145. /package/{src → build/src}/js/components/TabsSection.d.ts.map +0 -0
  146. /package/{src → build/src}/js/components/TitleWithTooltip.d.ts +0 -0
  147. /package/{src → build/src}/js/components/TitleWithTooltip.d.ts.map +0 -0
  148. /package/{src → build/src}/js/components/VideoLinks.d.ts +0 -0
  149. /package/{src → build/src}/js/components/VideoLinks.d.ts.map +0 -0
  150. /package/{src → build/src}/js/components/VideoPlayer.d.ts +0 -0
  151. /package/{src → build/src}/js/components/VideoPlayer.d.ts.map +0 -0
  152. /package/{src → build/src}/js/components/index.d.ts +0 -0
  153. /package/{src → build/src}/js/components/index.d.ts.map +0 -0
  154. /package/{src → build/src}/js/components/prism/PrismLiveModal.d.ts +0 -0
  155. /package/{src → build/src}/js/components/prism/index.d.ts +0 -0
  156. /package/{src → build/src}/js/components/prism/index.d.ts.map +0 -0
  157. /package/{src → build/src}/js/constants/baseUrls.d.ts +0 -0
  158. /package/{src → build/src}/js/constants/baseUrls.d.ts.map +0 -0
  159. /package/{src → build/src}/js/constants/embed.d.ts +0 -0
  160. /package/{src → build/src}/js/constants/embed.d.ts.map +0 -0
  161. /package/{src → build/src}/js/constants/player.d.ts +0 -0
  162. /package/{src → build/src}/js/constants/player.d.ts.map +0 -0
  163. /package/{src → build/src}/js/constants/pluginsRegistry.d.ts +0 -0
  164. /package/{src → build/src}/js/constants/pluginsRegistry.d.ts.map +0 -0
  165. /package/{src → build/src}/js/hooks/useGetPlayers.d.ts +0 -0
  166. /package/{src → build/src}/js/hooks/useGetPlayers.d.ts.map +0 -0
  167. /package/{src → build/src}/js/services/players-api.d.ts +0 -0
  168. /package/{src → build/src}/js/services/players-api.d.ts.map +0 -0
  169. /package/{src → build/src}/js/services/url-shortener.d.ts +0 -0
  170. /package/{src → build/src}/js/services/url-shortener.d.ts.map +0 -0
  171. /package/{src → build/src}/js/types/EmbedCodeModalProps.d.ts +0 -0
  172. /package/{src → build/src}/js/types/EmbedCodeModalProps.d.ts.map +0 -0
  173. /package/{src → build/src}/js/types/EmbedCodeProps.d.ts +0 -0
  174. /package/{src → build/src}/js/types/EmbedCodeProps.d.ts.map +0 -0
  175. /package/{src → build/src}/js/types/players.d.ts +0 -0
  176. /package/{src → build/src}/js/types/players.d.ts.map +0 -0
  177. /package/{src → build/src}/js/types/studioModule.d.ts +0 -0
  178. /package/{src → build/src}/js/types/studioModule.d.ts.map +0 -0
  179. /package/{src → build/src}/js/utils/addPluginsDataToPlayers.d.ts +0 -0
  180. /package/{src → build/src}/js/utils/addPluginsDataToPlayers.d.ts.map +0 -0
  181. /package/{src → build/src}/js/utils/call.d.ts +0 -0
  182. /package/{src → build/src}/js/utils/call.d.ts.map +0 -0
  183. /package/{src → build/src}/js/utils/dimensions.d.ts +0 -0
  184. /package/{src → build/src}/js/utils/dimensions.d.ts.map +0 -0
  185. /package/{src → build/src}/js/utils/embedCodeGenerator.d.ts +0 -0
  186. /package/{src → build/src}/js/utils/embedCodeGenerator.d.ts.map +0 -0
  187. /package/{src → build/src}/js/utils/featureSwitches.d.ts +0 -0
  188. /package/{src → build/src}/js/utils/featureSwitches.d.ts.map +0 -0
  189. /package/{src → build/src}/js/utils/util.d.ts +0 -0
  190. /package/{src → build/src}/js/utils/util.d.ts.map +0 -0
  191. /package/{src → build/src}/module.d.ts +0 -0
  192. /package/{src → build/src}/module.d.ts.map +0 -0
  193. /package/{src → build/src}/styles/typings.td.d.ts +0 -0
  194. /package/{src → build/src}/styles/typings.td.d.ts.map +0 -0
  195. /package/{src → build/src}/types/studio.d.ts +0 -0
  196. /package/{src → build/src}/types/studio.d.ts.map +0 -0
@@ -0,0 +1,356 @@
1
+ import React, { useState, useCallback, useEffect, 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
+ onEmbedCodeChange?: (code: string) => void;
44
+ }
45
+
46
+ export const PrismPlayerControls = ({
47
+ video,
48
+ players,
49
+ playerListForSelection,
50
+ selectedPlayer,
51
+ onPlayerChange,
52
+ embedProperties,
53
+ setEmbedProperties,
54
+ showDVR,
55
+ onDVRClick,
56
+ dvrTime,
57
+ showLowLatency,
58
+ onLowLatencyClick,
59
+ adConfigValue,
60
+ onAdConfigChange,
61
+ ssai,
62
+ adConfigOptions = [],
63
+ onEmbedCodeChange,
64
+ }: PrismPlayerControlsProps) => {
65
+ const { t } = useTranslation();
66
+ const [prevAspectRatio, setPrevAspectRatio] = useState(
67
+ embedProperties.aspectRatio,
68
+ );
69
+ const [aspectRatioLocked, setAspectRatioLocked] = useState(
70
+ embedProperties.aspectRatio !== EmbedCodeAspectRatio.CUSTOM,
71
+ );
72
+ const [dvrEnabled, setDvrEnabled] = useState(!showLowLatency);
73
+ const [lowLatencyEnabled, setLowLatencyEnabled] = useState(true);
74
+
75
+ const handlePropertiesUpdate = useCallback(
76
+ (name: string, value: string | boolean | number) => {
77
+ if (name === 'aspectRatio') {
78
+ const calculateNewWidth = shouldUpdateWidth(
79
+ prevAspectRatio,
80
+ value as EmbedCodeAspectRatio,
81
+ );
82
+
83
+ setPrevAspectRatio(value as EmbedCodeAspectRatio);
84
+ setAspectRatioLocked(value !== EmbedCodeAspectRatio.CUSTOM);
85
+
86
+ const dimensions = calculateDimensions(
87
+ calculateNewWidth,
88
+ value as EmbedCodeAspectRatio,
89
+ embedProperties.width,
90
+ embedProperties.height,
91
+ );
92
+
93
+ setEmbedProperties({
94
+ ...embedProperties,
95
+ aspectRatio: value as EmbedCodeAspectRatio,
96
+ width: dimensions.width as number,
97
+ height: dimensions.height,
98
+ });
99
+
100
+ return;
101
+ } else if (name === 'width' && aspectRatioLocked) {
102
+ const height = calculateHeight(
103
+ value as number,
104
+ embedProperties.aspectRatio,
105
+ );
106
+
107
+ setEmbedProperties({
108
+ ...embedProperties,
109
+ width: value as number,
110
+ height,
111
+ });
112
+
113
+ return;
114
+ }
115
+
116
+ setEmbedProperties({
117
+ ...embedProperties,
118
+ [name]: value,
119
+ });
120
+ },
121
+ [prevAspectRatio, aspectRatioLocked, embedProperties, setEmbedProperties],
122
+ );
123
+
124
+ const toggleAspectRatioLock = useCallback(() => {
125
+ if (embedProperties.aspectRatio === EmbedCodeAspectRatio.CUSTOM) {
126
+ return;
127
+ }
128
+ setAspectRatioLocked(!aspectRatioLocked);
129
+ }, [aspectRatioLocked, embedProperties.aspectRatio]);
130
+
131
+ // Player dropdown items
132
+ const playerItems = playerListForSelection.map((player) => ({
133
+ value: player.value,
134
+ label: player.label,
135
+ }));
136
+
137
+ // Sizing dropdown items
138
+ const sizingItems = [
139
+ { value: 'responsive', label: 'Responsive' },
140
+ { value: 'fixed', label: 'Fixed' },
141
+ ];
142
+
143
+ // Aspect ratio dropdown items
144
+ const aspectRatioItems = [
145
+ { value: EmbedCodeAspectRatio.SIXTEEN_NINE, label: '16:9 (Desktop)' },
146
+ { value: EmbedCodeAspectRatio.NINE_SIXTEEN, label: '9:16 (Mobile)' },
147
+ { value: EmbedCodeAspectRatio.ONE_ONE, label: '1:1' },
148
+ { value: EmbedCodeAspectRatio.FOUR_THREE, label: '4:3' },
149
+ { value: EmbedCodeAspectRatio.CUSTOM, label: 'Custom' },
150
+ ];
151
+
152
+ // Generate embed code
153
+ const player = players[selectedPlayer];
154
+ const { embedCode, embedCodeJS } = generateEmbedCode({
155
+ accountId: video.accountId,
156
+ playerId: selectedPlayer,
157
+ videoId: video.videoId,
158
+ embedProperties,
159
+ isCampaign: player?.hasCampaignPlugin || false,
160
+ isViewerIdSource: player?.hasViewerIDSource || false,
161
+ isBcMapTracking: player?.hasBcMapTracking || false,
162
+ livePlaybackToken: video.livePlaybackToken,
163
+ adConfigId: adConfigValue || video.adConfigId,
164
+ });
165
+
166
+ const requiresJsEmbed = Boolean(
167
+ player?.hasCampaignPlugin ||
168
+ player?.hasViewerIDSource ||
169
+ player?.hasBcMapTracking,
170
+ );
171
+
172
+ useEffect(() => {
173
+ onEmbedCodeChange?.(requiresJsEmbed ? embedCodeJS : embedCode);
174
+ }, [embedCode, embedCodeJS, requiresJsEmbed, onEmbedCodeChange]);
175
+
176
+ const codeCardTabs = useMemo(
177
+ () => [
178
+ {
179
+ label: (t('tabs.labels.iframe') as string) || 'iframe',
180
+ code: embedCode,
181
+ language: 'html',
182
+ },
183
+ {
184
+ label: (t('tabs.labels.javascript') as string) || 'JavaScript',
185
+ code: embedCodeJS,
186
+ language: 'javascript',
187
+ },
188
+ ],
189
+ [embedCode, embedCodeJS, t],
190
+ );
191
+
192
+ // Warning message for custom plugins
193
+ const warningMessage: string | undefined =
194
+ player?.hasCampaignPlugin || player?.hasViewerIDSource
195
+ ? (t('customPluginNotice') as string)
196
+ : void 0;
197
+
198
+ const handleDVRChange = useCallback(
199
+ (checked: boolean) => {
200
+ setDvrEnabled(checked);
201
+ if (onDVRClick) {
202
+ onDVRClick(checked);
203
+ }
204
+ },
205
+ [onDVRClick],
206
+ );
207
+
208
+ const handleLowLatencyChange = useCallback(
209
+ (checked: boolean) => {
210
+ setLowLatencyEnabled(checked);
211
+ if (onLowLatencyClick) {
212
+ onLowLatencyClick(checked);
213
+ }
214
+ },
215
+ [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
+ <div className={css.prismSizingField}>
251
+ <PrismInput
252
+ variant='FieldGroup'
253
+ fullWidth
254
+ label={
255
+ (t('customSettings.playerWidth') as string) || 'Player Width'
256
+ }
257
+ type='number'
258
+ value={String(embedProperties.width)}
259
+ onChange={(value) => handlePropertiesUpdate('width', Number(value))}
260
+ groupTitle='Unit'
261
+ groupValue={embedProperties.units}
262
+ onGroupChange={(unit) => handlePropertiesUpdate('units', unit)}
263
+ groupOptions={['px', '%']}
264
+ />
265
+ </div>
266
+
267
+ <PrismIconButton
268
+ icon='link'
269
+ variant='Ghost'
270
+ size='small'
271
+ onClick={toggleAspectRatioLock}
272
+ disabled={embedProperties.aspectRatio === EmbedCodeAspectRatio.CUSTOM}
273
+ className={css.prismLinkButton}
274
+ />
275
+
276
+ <div className={css.prismSizingField}>
277
+ <PrismInput
278
+ variant='FieldGroup'
279
+ fullWidth
280
+ label={
281
+ (t('customSettings.playerHeight') as string) || 'Player Height'
282
+ }
283
+ type='number'
284
+ value={String(embedProperties.height)}
285
+ onChange={(value) =>
286
+ handlePropertiesUpdate('height', Number(value))
287
+ }
288
+ disabled={aspectRatioLocked}
289
+ groupTitle='Unit'
290
+ groupValue={embedProperties.units}
291
+ onGroupChange={(unit) => handlePropertiesUpdate('units', unit)}
292
+ groupOptions={['px', '%']}
293
+ />
294
+ </div>
295
+ </div>
296
+
297
+ {/* DVR Checkbox (conditional) */}
298
+ {showDVR && (
299
+ <div>
300
+ <PrismCheckbox
301
+ label={(t('dvr.allowDVR') as string) || 'Enable DVR'}
302
+ checked={dvrEnabled}
303
+ onChange={(e) => handleDVRChange(e.target.checked)}
304
+ disabled={false}
305
+ />
306
+ {dvrEnabled && typeof dvrTime === 'number' && (
307
+ <div className={css.prismInputGroup}>
308
+ <PrismInput
309
+ variant='FieldGroup'
310
+ fullWidth
311
+ label={(t('dvr.inputLabel') as string) || 'DVR Window'}
312
+ type='number'
313
+ value={String(dvrTime)}
314
+ disabled
315
+ groupTitle='Unit'
316
+ groupValue={(t('dvr.unit') as string) || 'sec'}
317
+ groupOptions={[(t('dvr.unit') as string) || 'sec']}
318
+ />
319
+ </div>
320
+ )}
321
+ </div>
322
+ )}
323
+
324
+ {/* Low Latency Checkbox (conditional) */}
325
+ {showLowLatency && (
326
+ <PrismCheckbox
327
+ label={(t('lowLatency') as string) || 'Enable Low Latency'}
328
+ checked={lowLatencyEnabled}
329
+ onChange={(e) => handleLowLatencyChange(e.target.checked)}
330
+ disabled={false}
331
+ />
332
+ )}
333
+
334
+ {/* Ad Config Dropdown (conditional) */}
335
+ {ssai && adConfigOptions.length > 0 && onAdConfigChange && (
336
+ <PrismDropDown
337
+ label={(t('adConfiguration') as string) || 'Ad Configuration'}
338
+ items={adConfigOptions.map((opt) => ({
339
+ value: opt.value,
340
+ label: opt.label,
341
+ }))}
342
+ value={adConfigValue || adConfigOptions[0].value}
343
+ onChange={(item) => onAdConfigChange(item.value as string)}
344
+ />
345
+ )}
346
+
347
+ {warningMessage && (
348
+ <div className={css.prismCustomPluginNotice}>{warningMessage}</div>
349
+ )}
350
+
351
+ <div className={css.prismCodeCard}>
352
+ <PrismCodeCard indefiniteGrow tabs={codeCardTabs} />
353
+ </div>
354
+ </div>
355
+ );
356
+ };
@@ -0,0 +1,200 @@
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import ReactPlayerLoader from '@brightcove/react-player-loader';
9
+ import { PrismButton, PrismButtonDropdown } from '@brightcove/prism-core';
10
+ import { getBrightcove, getUrlBase } from 'js/utils/util';
11
+ import { useTranslation } from 'react-i18next';
12
+
13
+ import { getUrlAndAddItToClipboard } from '../VideoLinks';
14
+
15
+ import css from 'styles/prism/PrismLiveModal.sass';
16
+
17
+ interface PrismPlayerPreviewProps {
18
+ accountId: string;
19
+ playerId: string;
20
+ videoId: string;
21
+ previewUrl: string;
22
+ livePlaybackToken?: string;
23
+ adConfigId?: string;
24
+ isMobile?: boolean;
25
+ }
26
+
27
+ export const PrismPlayerPreview = ({
28
+ accountId,
29
+ playerId,
30
+ videoId,
31
+ previewUrl,
32
+ isMobile,
33
+ }: PrismPlayerPreviewProps) => {
34
+ const { t } = useTranslation();
35
+ const [isShorteningUrl, setIsShorteningUrl] = useState<boolean>(false);
36
+ const [isCopied, setIsCopied] = useState<boolean>(false);
37
+ const copiedTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
38
+ const shortenRequestRef = useRef<number>(0);
39
+
40
+ useEffect(
41
+ () => () => {
42
+ shortenRequestRef.current += 1;
43
+ if (copiedTimeoutRef.current) {
44
+ clearTimeout(copiedTimeoutRef.current);
45
+ }
46
+ setIsShorteningUrl(false);
47
+ setIsCopied(false);
48
+ },
49
+ [previewUrl],
50
+ );
51
+
52
+ const notifyMissingPreviewUrl = useCallback(() => {
53
+ getBrightcove().Toaster.error({
54
+ title: t('toasterTitle') as string,
55
+ message: 'Please select a player first',
56
+ });
57
+ }, [t]);
58
+
59
+ const showCopiedFeedback = useCallback(() => {
60
+ if (copiedTimeoutRef.current) {
61
+ clearTimeout(copiedTimeoutRef.current);
62
+ }
63
+ setIsCopied(true);
64
+ copiedTimeoutRef.current = setTimeout(() => setIsCopied(false), 2000);
65
+ }, []);
66
+
67
+ const handleCopyFullUrl = useCallback(() => {
68
+ if (!previewUrl) {
69
+ notifyMissingPreviewUrl();
70
+ return;
71
+ }
72
+ navigator.clipboard
73
+ .writeText(previewUrl)
74
+ .then(showCopiedFeedback)
75
+ .catch(() => {
76
+ getBrightcove().Toaster.error({
77
+ message: t('toasterErrorMessage') as string,
78
+ });
79
+ });
80
+ }, [previewUrl, t, notifyMissingPreviewUrl, showCopiedFeedback]);
81
+
82
+ const handleCopyShortUrl = useCallback(() => {
83
+ if (!previewUrl) {
84
+ notifyMissingPreviewUrl();
85
+ return;
86
+ }
87
+ const requestId = shortenRequestRef.current + 1;
88
+ shortenRequestRef.current = requestId;
89
+ const isCurrentRequest = () => shortenRequestRef.current === requestId;
90
+
91
+ setIsShorteningUrl(true);
92
+ getUrlAndAddItToClipboard(previewUrl)
93
+ .then(() => {
94
+ if (isCurrentRequest()) {
95
+ showCopiedFeedback();
96
+ }
97
+ })
98
+ .catch(() => {
99
+ if (isCurrentRequest()) {
100
+ getBrightcove().Toaster.error({
101
+ message: t('toasterGetURLError') as string,
102
+ });
103
+ }
104
+ })
105
+ .finally(() => {
106
+ if (isCurrentRequest()) {
107
+ setIsShorteningUrl(false);
108
+ }
109
+ });
110
+ }, [previewUrl, t, notifyMissingPreviewUrl, showCopiedFeedback]);
111
+
112
+ const copyUrlOptions = useMemo(
113
+ () => [
114
+ {
115
+ id: 'short-url',
116
+ label: t('prism.shortURL') as string,
117
+ icon: 'LAYERS_SUBTRACT',
118
+ onClick: handleCopyShortUrl,
119
+ disabled: isShorteningUrl,
120
+ },
121
+ {
122
+ id: 'full-url',
123
+ label: t('prism.fullURL') as string,
124
+ icon: 'LAYERS_SUBTRACT',
125
+ onClick: handleCopyFullUrl,
126
+ },
127
+ ],
128
+ [t, handleCopyShortUrl, handleCopyFullUrl, isShorteningUrl],
129
+ );
130
+
131
+ const copyUrlLabel = () => {
132
+ if (isCopied) {
133
+ return t('prism.copied') as string;
134
+ }
135
+ if (isShorteningUrl) {
136
+ return t('prism.copying') as string;
137
+ }
138
+ return isMobile
139
+ ? (t('urlShort') as string)
140
+ : (t('prism.copyURL') as string);
141
+ };
142
+
143
+ const handleOpenPreview = useCallback(() => {
144
+ if (!previewUrl) {
145
+ notifyMissingPreviewUrl();
146
+ return;
147
+ }
148
+ window.open(previewUrl, '_blank', 'noopener,noreferrer');
149
+ }, [previewUrl, notifyMissingPreviewUrl]);
150
+
151
+ // Build player options for Live modal
152
+ // Note: Following Bluesteel pattern - we DON'T pass adConfigId or livePlaybackToken
153
+ // to ReactPlayerLoader. These are only used in the previewUrl for external links
154
+ // and in the embed code generation, not for the embedded player preview.
155
+ const playerOptions: Record<string, string> = {
156
+ accountId,
157
+ playerId,
158
+ videoId,
159
+ };
160
+
161
+ return (
162
+ <div className={css.prismPlayerPreviewContainer}>
163
+ <div className={css.prismPlayerActions}>
164
+ <PrismButtonDropdown
165
+ variant={isMobile ? 'Secondary' : 'Primary'}
166
+ {...(isMobile ? { size: 'mobile' as const, fullWidth: true } : {})}
167
+ options={copyUrlOptions}
168
+ modalTitle={t('prism.copyURL') as string}
169
+ showDropdownIcon={!isCopied && !isShorteningUrl}
170
+ {...(isCopied ? { rightIcon: 'CHECK' } : {})}
171
+ disabled={!previewUrl || isShorteningUrl || isCopied}
172
+ >
173
+ {copyUrlLabel()}
174
+ </PrismButtonDropdown>
175
+ <PrismButton
176
+ variant={isMobile ? 'Secondary' : 'Primary'}
177
+ {...(isMobile ? { size: 'mobile' as const, fullWidth: true } : {})}
178
+ onClick={handleOpenPreview}
179
+ rightIcon='link'
180
+ disabled={!previewUrl}
181
+ >
182
+ {isMobile
183
+ ? (t('previewShort') as string)
184
+ : (t('previewButton') as string)}
185
+ </PrismButton>
186
+ </div>
187
+
188
+ <div className={css.prismPlayerWrapper}>
189
+ <ReactPlayerLoader
190
+ key={`${accountId}-${playerId}-${videoId}`}
191
+ {...playerOptions}
192
+ baseUrl={getUrlBase()}
193
+ attrs={{ className: css.playerLoader }}
194
+ embedType='iframe'
195
+ refNode='player-container'
196
+ />
197
+ </div>
198
+ </div>
199
+ );
200
+ };
@@ -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;
@@ -0,0 +1,33 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ const MOBILE_MEDIA_QUERY = '(max-width: 1023px)';
4
+
5
+ const getMatches = (): boolean =>
6
+ typeof window !== 'undefined' && typeof window.matchMedia === 'function'
7
+ ? window.matchMedia(MOBILE_MEDIA_QUERY).matches
8
+ : false;
9
+
10
+ const useIsMobile = (): boolean => {
11
+ const [isMobile, setIsMobile] = useState<boolean>(getMatches);
12
+
13
+ useEffect(() => {
14
+ if (
15
+ typeof window === 'undefined' ||
16
+ typeof window.matchMedia !== 'function'
17
+ ) {
18
+ return;
19
+ }
20
+
21
+ const mql = window.matchMedia(MOBILE_MEDIA_QUERY);
22
+ const handleChange = (event: MediaQueryListEvent) =>
23
+ setIsMobile(event.matches);
24
+
25
+ setIsMobile(mql.matches);
26
+ mql.addEventListener('change', handleChange);
27
+ return () => mql.removeEventListener('change', handleChange);
28
+ }, []);
29
+
30
+ return isMobile;
31
+ };
32
+
33
+ export default useIsMobile;