@clickview/player 0.0.0-rc.4 → 0.0.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/.eslintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@clickview/eslint-config"]
3
+ }
@@ -0,0 +1,22 @@
1
+ const custom = require('../tooling/webpack.config');
2
+
3
+ module.exports = {
4
+ stories: [
5
+ './**/*.stories.@(js|jsx|ts|tsx)'
6
+ ],
7
+ webpackFinal: (config) => {
8
+ return {
9
+ ...config,
10
+ context: custom.context,
11
+ module: {
12
+ ...config.module,
13
+ rules: custom.module.rules
14
+ },
15
+ resolve: custom.resolve,
16
+ plugins: [
17
+ ...config.plugins,
18
+ ...custom.plugins
19
+ ]
20
+ };
21
+ },
22
+ };
@@ -0,0 +1,3 @@
1
+ <!-- TODO: Link to stylesheet -->
2
+ <!-- <link rel="stylesheet" href="player-app.css"> -->
3
+ <link rel="stylesheet" href="https://online.clickview.com.au/Assets/css/compiled/modules/styles/cv-styles.css?v=7.35.0">
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import { ClickViewPlayer, Player } from '../../src/index';
3
+ import { mockplayerApi } from './utils/mock-playerapi';
4
+ import { mockSetups } from './utils/mock-setup';
5
+
6
+ export default {
7
+ title: 'Base'
8
+ };
9
+
10
+ export const Default = () =>
11
+ <ClickViewPlayer viewKey='basic' fetch={mockplayerApi} />;
12
+
13
+ Default.storyName = 'Default';
14
+
15
+ export const Chapters = () =>
16
+ <ClickViewPlayer viewKey='chapters' fetch={mockplayerApi} />;
17
+
18
+ Chapters.storyName = 'Chapters';
19
+
20
+ export const Thumbnails = () =>
21
+ <ClickViewPlayer viewKey='chapters' fetch={mockplayerApi} setup={mockSetups.thumbnails} />;
22
+
23
+ Thumbnails.storyName = 'Thumbnails';
24
+
25
+ export const Vertical = () => (
26
+ <div style={{ width: '300px' }}>
27
+ <ClickViewPlayer viewKey='vertical' fetch={mockplayerApi} />
28
+ </div>
29
+ );
30
+
31
+ Vertical.storyName = 'Vertical';
32
+
33
+ export const NoControls = () =>
34
+ <ClickViewPlayer viewKey='basic' fetch={mockplayerApi} controls={false} />;
35
+
36
+ NoControls.storyName = 'No Controls';
37
+
38
+ export const OpenExternally = () =>
39
+ <ClickViewPlayer
40
+ viewKey='basic'
41
+ fetch={mockplayerApi}
42
+ getExternalLink={(options: any) => `https://online.clickview.com.au/share?sharecode=1ef15209&t=${options.currentTime}`}
43
+ />;
44
+
45
+ OpenExternally.storyName = 'Open Externally';
46
+
47
+ export const Error = () =>
48
+ <ClickViewPlayer viewKey='error' fetch={mockplayerApi} />;
49
+
50
+ export const PlayerApi = () =>
51
+ <ClickViewPlayer viewKey='camwashere' />;
52
+
53
+ PlayerApi.storyName = 'Player API';
54
+
55
+ /**
56
+ * This story uses an intentionally invalid viewKey.
57
+ */
58
+ export const PlayerApiError = () =>
59
+ <ClickViewPlayer viewKey='camwashere_error' />;
60
+
61
+ PlayerApiError.storyName = 'Player API Error';
62
+
63
+ export const MultipleQualities = () =>
64
+ <ClickViewPlayer viewKey='multipleQualities' fetch={mockplayerApi} />;
65
+
66
+ export const Poster = () =>
67
+ <ClickViewPlayer viewKey='poster' fetch={mockplayerApi} />;
68
+
69
+ export const Everything = () =>
70
+ <ClickViewPlayer
71
+ viewKey='everything'
72
+ fetch={mockplayerApi}
73
+ setup={mockSetups.everything}
74
+ getExternalLink={(options: any) => `https://online.clickview.com.au/share?sharecode=1ef15209&t=${options.currentTime}`}
75
+ />;
76
+
77
+ export const LocalCache = () => <ClickViewPlayer viewKey='localcache' fetch={mockplayerApi} />;
78
+
79
+ export const StyleGuideline = () => {
80
+ const [ hidden, setHidden ] = React.useState(false);
81
+
82
+ function onClick(): void {
83
+ setHidden(!hidden);
84
+ }
85
+
86
+ return (
87
+ <div
88
+ style={{
89
+ width: '1100px',
90
+ height: '619px',
91
+ position: 'relative'
92
+ }}
93
+ >
94
+ <div
95
+ style={{
96
+ position: 'absolute',
97
+ top: '0px',
98
+ left: '0px',
99
+ right: '0px',
100
+ bottom: '0px',
101
+ backgroundImage: 'url(https://i.imgur.com/nqKupnk.png)',
102
+ zIndex: 2,
103
+ display: hidden ? 'none' : 'block'
104
+ }}
105
+ ></div>
106
+ <div style={{ position: 'relative', zIndex: 1 }}>
107
+ <ClickViewPlayer
108
+ viewKey='everything'
109
+ fetch={mockplayerApi}
110
+ setup={mockSetups.everything}
111
+ />
112
+ </div>
113
+
114
+ <button onClick={onClick}>Toggle</button>
115
+ </div>
116
+ )
117
+ }
118
+
119
+ export const PlayNextVideo = () => {
120
+ return <ClickViewPlayer
121
+ // viewKey='basic'
122
+ viewKey='chapters'
123
+ fetch={mockplayerApi}
124
+ onReady={(player: Player) => {
125
+ player.setNextVideo({
126
+ name: 'The Equalizer',
127
+ thumbnailUrl: 'https://image.api.stg.cvnet.io/v2/thumbnails/Q229lo'
128
+ }, () => {
129
+ console.log('playNextVideo')
130
+ });
131
+ }} />;
132
+ }
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { ClickViewPlayer, ClickViewCreateClipPlayer } from '../../src/index';
3
+ import { mockplayerApi } from './utils/mock-playerapi';
4
+
5
+ interface ClipFormProps {
6
+ startTime?: number;
7
+ endTime?: number;
8
+ minDuration?: number;
9
+ }
10
+
11
+ function ClipForm(props: ClipFormProps): JSX.Element {
12
+ const [ startTime, setStartTime ] = React.useState(props.startTime);
13
+ const [ endTime, setEndTime ] = React.useState(props.endTime);
14
+
15
+ const getInputValue = (time?: number) => {
16
+ return isNaN(time)
17
+ ? ''
18
+ : time;
19
+ };
20
+
21
+ return (
22
+ <>
23
+ <ClickViewCreateClipPlayer
24
+ viewKey='basic'
25
+ fetch={mockplayerApi}
26
+ startTime={startTime}
27
+ onChangeStartTime={setStartTime}
28
+ endTime={endTime}
29
+ onChangeEndTime={setEndTime}
30
+ minDuration={props.minDuration}
31
+ />
32
+ <div style={{ marginTop: '15px' }}>
33
+ <label>Start Time (s)</label>
34
+ <input type='number' value={getInputValue(startTime)} onChange={e => setStartTime(+e.target.value)} />
35
+ <label>End Time (s)</label>
36
+ <input type='number' value={getInputValue(endTime)} onChange={e => setEndTime(+e.target.value)} />
37
+ <label>Duration (s)</label>
38
+ <input type='number' value={getInputValue(endTime - startTime)} readOnly />
39
+ </div>
40
+ </>
41
+ );
42
+ }
43
+
44
+ export default {
45
+ title: 'Clips'
46
+ };
47
+
48
+ export const ClipPlayback = () =>
49
+ <ClickViewPlayer viewKey='clip' fetch={mockplayerApi} />;
50
+
51
+ ClipPlayback.storyName = 'Clip Playback';
52
+
53
+ export const CreateClip = () => <ClipForm />;
54
+
55
+ CreateClip.storyName = 'Create Clip';
56
+
57
+ export const EditClip = () => <ClipForm startTime={100} endTime={200} />;
58
+
59
+ EditClip.storyName = 'Edit Clip';
60
+
61
+ export const MinDurationClip = () => <ClipForm minDuration={30} />;
62
+
63
+ MinDurationClip.storyName = 'Create Clip with 30s min duration';
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { ClickViewInteractivePlayer, InteractivePlayer } from '../../src/index';
3
+ import { InteractiveMode } from '../../src/plugins/interactive-plugin/interfaces';
4
+ import { mockplayerApi } from './utils/mock-playerapi';
5
+
6
+ export default {
7
+ title: 'Interactives'
8
+ };
9
+
10
+ export const Default = () =>
11
+ <ClickViewInteractivePlayer viewKey='interactive' fetch={mockplayerApi} />;
12
+
13
+ export const Create = () =>
14
+ <ClickViewInteractivePlayer
15
+ viewKey='basic'
16
+ fetch={mockplayerApi}
17
+ mode={InteractiveMode.Create} />;
18
+
19
+ export const Annotation = () => {
20
+ function onReady(player: InteractivePlayer): void {
21
+ player.currentTime(15);
22
+ }
23
+
24
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
25
+ };
26
+
27
+ export const ShortAnswer = () => {
28
+ function onReady(player: InteractivePlayer): void {
29
+ player.currentTime(13);
30
+ }
31
+
32
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
33
+ };
34
+
35
+ export const Image = () => {
36
+ function onReady(player: InteractivePlayer): void {
37
+ player.currentTime(17);
38
+ }
39
+
40
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
41
+ };
42
+
43
+ export const TrueOrFalse = () => {
44
+ function onReady(player: InteractivePlayer): void {
45
+ player.currentTime(5);
46
+ }
47
+
48
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
49
+ };
50
+
51
+ export const MultipleChoice = () => {
52
+ function onReady(player: InteractivePlayer): void {
53
+ player.currentTime(7);
54
+ }
55
+
56
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
57
+ };
58
+
59
+ export const MissingWord = () => {
60
+ function onReady(player: InteractivePlayer): void {
61
+ player.currentTime(9);
62
+ }
63
+
64
+ return <ClickViewInteractivePlayer onReady={onReady} viewKey='interactive' fetch={mockplayerApi} autoplay={true} />;
65
+ };
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { ClickViewPlayer } from '../../src/index';
3
+ import { mockplayerApi } from './utils/mock-playerapi';
4
+
5
+ export default {
6
+ title: 'Subtitles'
7
+ };
8
+
9
+ export const SingleTrack = () =>
10
+ <ClickViewPlayer viewKey='basic' fetch={mockplayerApi} />;
11
+
12
+ SingleTrack.storyName = 'Single Track';
13
+
14
+ export const MultipleTracksWithDefault = () =>
15
+ <ClickViewPlayer viewKey='multipleTracksWithDefault' fetch={mockplayerApi} />;
16
+
17
+ MultipleTracksWithDefault.storyName = 'Multiple Tracks with default';
18
+
19
+ export const MultipleTracks = () =>
20
+ <ClickViewPlayer viewKey='multipleSubtitles' fetch={mockplayerApi} />;
21
+
22
+ MultipleTracks.storyName = 'Multiple Tracks';
@@ -0,0 +1,19 @@
1
+ WEBVTT
2
+
3
+ 00:00.000 --> 00:05.000
4
+ Waves
5
+
6
+ 00:05.000 --> 00:11.000
7
+ Some More Waves
8
+
9
+ 00:11.000 --> 00:13.000
10
+ Waves hitting Rocks
11
+
12
+ 00:13.000 --> 00:20.000
13
+ Rocky Rocks
14
+
15
+ 00:20.000 --> 00:26.000
16
+ More Rocks
17
+
18
+ 00:26.000 --> 00:28.000
19
+ End of waves
@@ -0,0 +1,8 @@
1
+ WEBVTT
2
+
3
+ 00:01.000 --> 00:15.000
4
+ - Never drink liquid nitrogen.
5
+
6
+ 00:17.000 --> 00:25.000
7
+ - It will perforate your stomach.
8
+ - You could die.
@@ -0,0 +1,8 @@
1
+ WEBVTT
2
+
3
+ 00:01.000 --> 00:15.000
4
+ - Nunca beba nitrógeno líquido.
5
+
6
+ 00:17.000 --> 00:25.000
7
+ - Perforará tu estómago.
8
+ - Podrías morir.
@@ -0,0 +1,8 @@
1
+ WEBVTT
2
+
3
+ 00:01.000 --> 00:15.000
4
+ - Ne buvez jamais d'azote liquide.
5
+
6
+ 00:17.000 --> 00:25.000
7
+ - Il perforera votre estomac.
8
+ - Tu pourrais mourir.
@@ -0,0 +1,451 @@
1
+ {
2
+ "timepoints": [{
3
+ "interactions": [{
4
+ "data": {
5
+ "sentence": "A news website keeping track of the number of clicks stories receive is an example of a {17} tool used to gather information."
6
+ },
7
+ "actionableItems": [{
8
+ "name": "digital",
9
+ "order": 17,
10
+ "isCorrect": true,
11
+ "interactionId": 704598,
12
+ "id": 1560253,
13
+ "deleted": false,
14
+ "dateCreated": "2020-11-04T23:27:03",
15
+ "dateDeleted": null,
16
+ "dateModified": null
17
+ }],
18
+ "name": "A news website keeping track of the number of clicks stories receive is an example of a _______ tool used to gather information.",
19
+ "order": 0,
20
+ "description": null,
21
+ "errorMessage": null,
22
+ "isRequired": false,
23
+ "interactiveId": 190303,
24
+ "timepointId": 709577,
25
+ "typeId": 6,
26
+ "type": {
27
+ "name": "Missing word",
28
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/missing-word-v2.png",
29
+ "colour": "51ada3",
30
+ "id": 6,
31
+ "deleted": false,
32
+ "dateCreated": "2015-10-19T09:01:50",
33
+ "dateDeleted": null,
34
+ "dateModified": null
35
+ },
36
+ "id": 704598,
37
+ "deleted": false,
38
+ "dateCreated": "0001-01-01T00:00:00",
39
+ "dateDeleted": null,
40
+ "dateModified": null
41
+ }],
42
+ "visibleAt": 2500,
43
+ "visibleFor": null,
44
+ "shouldPause": 1,
45
+ "interactiveId": 190303,
46
+ "id": 709577,
47
+ "deleted": false,
48
+ "dateCreated": "0001-01-01T00:00:00",
49
+ "dateDeleted": null,
50
+ "dateModified": null
51
+ }, {
52
+ "interactions": [{
53
+ "data": {},
54
+ "actionableItems": [{
55
+ "name": "True",
56
+ "order": 0,
57
+ "isCorrect": false,
58
+ "interactionId": 704603,
59
+ "id": 1560263,
60
+ "deleted": false,
61
+ "dateCreated": "2020-11-04T23:27:41",
62
+ "dateDeleted": null,
63
+ "dateModified": null
64
+ }, {
65
+ "name": "False",
66
+ "order": 1,
67
+ "isCorrect": true,
68
+ "interactionId": 704603,
69
+ "id": 1560262,
70
+ "deleted": false,
71
+ "dateCreated": "2020-11-04T23:27:41",
72
+ "dateDeleted": null,
73
+ "dateModified": null
74
+ }],
75
+ "name": "A 'cookie' is term used to describe personalised, targeted advertisements.",
76
+ "order": 0,
77
+ "description": null,
78
+ "errorMessage": null,
79
+ "isRequired": false,
80
+ "interactiveId": 190303,
81
+ "timepointId": 709582,
82
+ "typeId": 3,
83
+ "type": {
84
+ "name": "True or False",
85
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/true-or-false-v2.png",
86
+ "colour": "b18cbb",
87
+ "id": 3,
88
+ "deleted": false,
89
+ "dateCreated": "2015-10-19T09:01:50",
90
+ "dateDeleted": null,
91
+ "dateModified": null
92
+ },
93
+ "id": 704603,
94
+ "deleted": false,
95
+ "dateCreated": "0001-01-01T00:00:00",
96
+ "dateDeleted": null,
97
+ "dateModified": null
98
+ }],
99
+ "visibleAt": 4500,
100
+ "visibleFor": null,
101
+ "shouldPause": 1,
102
+ "interactiveId": 190303,
103
+ "id": 709582,
104
+ "deleted": false,
105
+ "dateCreated": "0001-01-01T00:00:00",
106
+ "dateDeleted": null,
107
+ "dateModified": null
108
+ }, {
109
+ "interactions": [{
110
+ "data": {},
111
+ "actionableItems": [{
112
+ "name": "Advertising",
113
+ "order": 0,
114
+ "isCorrect": true,
115
+ "interactionId": 704604,
116
+ "id": 1560270,
117
+ "deleted": false,
118
+ "dateCreated": "2020-11-04T23:28:33",
119
+ "dateDeleted": null,
120
+ "dateModified": null
121
+ }, {
122
+ "name": "Social media",
123
+ "order": 1,
124
+ "isCorrect": true,
125
+ "interactionId": 704604,
126
+ "id": 1560269,
127
+ "deleted": false,
128
+ "dateCreated": "2020-11-04T23:28:33",
129
+ "dateDeleted": null,
130
+ "dateModified": null
131
+ }, {
132
+ "name": "News and 'fake news'",
133
+ "order": 2,
134
+ "isCorrect": true,
135
+ "interactionId": 704604,
136
+ "id": 1560271,
137
+ "deleted": false,
138
+ "dateCreated": "2020-11-04T23:28:33",
139
+ "dateDeleted": null,
140
+ "dateModified": null
141
+ }, {
142
+ "name": "Election campaigns",
143
+ "order": 3,
144
+ "isCorrect": true,
145
+ "interactionId": 704604,
146
+ "id": 1560268,
147
+ "deleted": false,
148
+ "dateCreated": "2020-11-04T23:28:33",
149
+ "dateDeleted": null,
150
+ "dateModified": null
151
+ }],
152
+ "name": "Select all that apply. In what contexts might 'hyper-nudging' occur?",
153
+ "order": 0,
154
+ "description": null,
155
+ "errorMessage": null,
156
+ "isRequired": false,
157
+ "interactiveId": 190303,
158
+ "timepointId": 709583,
159
+ "typeId": 1,
160
+ "type": {
161
+ "name": "Multiple Choice",
162
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/multiple-choice-v2.png",
163
+ "colour": "629fc9",
164
+ "id": 1,
165
+ "deleted": false,
166
+ "dateCreated": "2015-10-19T09:01:50",
167
+ "dateDeleted": null,
168
+ "dateModified": null
169
+ },
170
+ "id": 704604,
171
+ "deleted": false,
172
+ "dateCreated": "0001-01-01T00:00:00",
173
+ "dateDeleted": null,
174
+ "dateModified": null
175
+ }],
176
+ "visibleAt": 6500,
177
+ "visibleFor": null,
178
+ "shouldPause": 1,
179
+ "interactiveId": 190303,
180
+ "id": 709583,
181
+ "deleted": false,
182
+ "dateCreated": "0001-01-01T00:00:00",
183
+ "dateDeleted": null,
184
+ "dateModified": null
185
+ }, {
186
+ "interactions": [{
187
+ "data": {
188
+ "sentence": "Cambridge Analytica had access not only to their own data, but also data made available to them by {18}."
189
+ },
190
+ "actionableItems": [{
191
+ "name": "Facebook",
192
+ "order": 18,
193
+ "isCorrect": true,
194
+ "interactionId": 704608,
195
+ "id": 1560286,
196
+ "deleted": false,
197
+ "dateCreated": "2020-11-04T23:29:22",
198
+ "dateDeleted": null,
199
+ "dateModified": null
200
+ }],
201
+ "name": "Cambridge Analytica had access not only to their own data, but also data made available to them by _________.",
202
+ "order": 0,
203
+ "description": null,
204
+ "errorMessage": null,
205
+ "isRequired": false,
206
+ "interactiveId": 190303,
207
+ "timepointId": 709587,
208
+ "typeId": 6,
209
+ "type": {
210
+ "name": "Missing word",
211
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/missing-word-v2.png",
212
+ "colour": "51ada3",
213
+ "id": 6,
214
+ "deleted": false,
215
+ "dateCreated": "2015-10-19T09:01:50",
216
+ "dateDeleted": null,
217
+ "dateModified": null
218
+ },
219
+ "id": 704608,
220
+ "deleted": false,
221
+ "dateCreated": "0001-01-01T00:00:00",
222
+ "dateDeleted": null,
223
+ "dateModified": null
224
+ }],
225
+ "visibleAt": 8500,
226
+ "visibleFor": null,
227
+ "shouldPause": 1,
228
+ "interactiveId": 190303,
229
+ "id": 709587,
230
+ "deleted": false,
231
+ "dateCreated": "0001-01-01T00:00:00",
232
+ "dateDeleted": null,
233
+ "dateModified": null
234
+ }, {
235
+ "interactions": [{
236
+ "data": {},
237
+ "actionableItems": [{
238
+ "name": "True",
239
+ "order": 0,
240
+ "isCorrect": true,
241
+ "interactionId": 704613,
242
+ "id": 1560300,
243
+ "deleted": false,
244
+ "dateCreated": "2020-11-04T23:30:27",
245
+ "dateDeleted": null,
246
+ "dateModified": null
247
+ }, {
248
+ "name": "False",
249
+ "order": 1,
250
+ "isCorrect": false,
251
+ "interactionId": 704613,
252
+ "id": 1560301,
253
+ "deleted": false,
254
+ "dateCreated": "2020-11-04T23:30:27",
255
+ "dateDeleted": null,
256
+ "dateModified": null
257
+ }],
258
+ "name": "Companies can combine information from multiple different digital sources to draw conclusions about people's behaviour.",
259
+ "order": 0,
260
+ "description": null,
261
+ "errorMessage": null,
262
+ "isRequired": false,
263
+ "interactiveId": 190303,
264
+ "timepointId": 709592,
265
+ "typeId": 3,
266
+ "type": {
267
+ "name": "True or False",
268
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/true-or-false-v2.png",
269
+ "colour": "b18cbb",
270
+ "id": 3,
271
+ "deleted": false,
272
+ "dateCreated": "2015-10-19T09:01:50",
273
+ "dateDeleted": null,
274
+ "dateModified": null
275
+ },
276
+ "id": 704613,
277
+ "deleted": false,
278
+ "dateCreated": "0001-01-01T00:00:00",
279
+ "dateDeleted": null,
280
+ "dateModified": null
281
+ }],
282
+ "visibleAt": 10500,
283
+ "visibleFor": null,
284
+ "shouldPause": 1,
285
+ "interactiveId": 190303,
286
+ "id": 709592,
287
+ "deleted": false,
288
+ "dateCreated": "0001-01-01T00:00:00",
289
+ "dateDeleted": null,
290
+ "dateModified": null
291
+ }, {
292
+ "interactions": [{
293
+ "data": {},
294
+ "actionableItems": [{
295
+ "name": "Answer",
296
+ "order": 0,
297
+ "isCorrect": null,
298
+ "interactionId": 704616,
299
+ "id": 1560311,
300
+ "deleted": false,
301
+ "dateCreated": "2020-11-04T23:32:59",
302
+ "dateDeleted": null,
303
+ "dateModified": null
304
+ }],
305
+ "name": "Think of a form of social media or an app that you use regularly. What personal details or other data do you think it has about you?",
306
+ "order": 0,
307
+ "description": null,
308
+ "errorMessage": null,
309
+ "isRequired": false,
310
+ "interactiveId": 190303,
311
+ "timepointId": 709595,
312
+ "typeId": 2,
313
+ "type": {
314
+ "name": "Short Answer",
315
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/short-answer-v2.png",
316
+ "colour": "73b18c",
317
+ "id": 2,
318
+ "deleted": false,
319
+ "dateCreated": "2015-10-19T09:01:50",
320
+ "dateDeleted": null,
321
+ "dateModified": null
322
+ },
323
+ "id": 704616,
324
+ "deleted": false,
325
+ "dateCreated": "0001-01-01T00:00:00",
326
+ "dateDeleted": null,
327
+ "dateModified": null
328
+ }],
329
+ "visibleAt": 12500,
330
+ "visibleFor": null,
331
+ "shouldPause": 1,
332
+ "interactiveId": 190303,
333
+ "id": 709595,
334
+ "deleted": false,
335
+ "dateCreated": "0001-01-01T00:00:00",
336
+ "dateDeleted": null,
337
+ "dateModified": null
338
+ },{
339
+ "interactions": [{
340
+ "data": {
341
+ "annotation": "\n Discussion Time!<br><br>Can you think of a choice you have made that felt like the RIGHT thing to do?&nbsp;<br><br>What were the consequences?<br><br>"
342
+ },
343
+ "actionableItems": [{
344
+ "name": "Answer",
345
+ "order": 0,
346
+ "isCorrect": null,
347
+ "interactionId": 702581,
348
+ "id": 1556100,
349
+ "deleted": false,
350
+ "dateCreated": "2020-11-03T21:34:21",
351
+ "dateDeleted": null,
352
+ "dateModified": null
353
+ }],
354
+ "name": "Annotation",
355
+ "order": 0,
356
+ "description": null,
357
+ "errorMessage": null,
358
+ "isRequired": false,
359
+ "interactiveId": 189978,
360
+ "timepointId": 707559,
361
+ "typeId": 4,
362
+ "type": {
363
+ "name": "Annotation",
364
+ "icon": "//static.clickview.com.au/cv-online/images/interactions/icons/annotation-v2.png",
365
+ "colour": "f07e7e",
366
+ "id": 4,
367
+ "deleted": false,
368
+ "dateCreated": "2015-10-19T09:01:50",
369
+ "dateDeleted": null,
370
+ "dateModified": null
371
+ },
372
+ "id": 702581,
373
+ "deleted": false,
374
+ "dateCreated": "0001-01-01T00:00:00",
375
+ "dateDeleted": null,
376
+ "dateModified": null
377
+ }],
378
+ "visibleAt": 14500,
379
+ "visibleFor": null,
380
+ "shouldPause": 1,
381
+ "interactiveId": 189978,
382
+ "id": 707559,
383
+ "deleted": false,
384
+ "dateCreated": "0001-01-01T00:00:00",
385
+ "dateDeleted": null,
386
+ "dateModified": null
387
+ },{
388
+ "interactions":[
389
+ {
390
+ "data":{
391
+ "url":"https://api-image.clickviewapp.com/v1/thumbnails/5920179",
392
+ "caption":"The dark green area on this map shows how large the Murray-Darling Basin is. This area alone is responsible for growing one third of Australia's entire food supply!"
393
+ },
394
+ "actionableItems":[
395
+ {
396
+ "name":"Viewed",
397
+ "order":0,
398
+ "isCorrect":true,
399
+ "interactionId":664721,
400
+ "id":1476619,
401
+ "deleted":false,
402
+ "dateCreated":"2020-09-24T05:31:04",
403
+ "dateDeleted":null,
404
+ "dateModified":null
405
+ }
406
+ ],
407
+ "name":"The dark green area on this map shows how large the Murray-Darling Basin is. This area alone is responsible for growing one third of Australia's entire food supply!",
408
+ "order":0,
409
+ "description":null,
410
+ "errorMessage":null,
411
+ "isRequired":false,
412
+ "interactiveId":183569,
413
+ "timepointId":669510,
414
+ "typeId":5,
415
+ "type":{
416
+ "name":"Image",
417
+ "icon":"//static.clickview.com.au/cv-online/images/interactions/icons/image-v2.png",
418
+ "colour":"f8981d",
419
+ "id":5,
420
+ "deleted":false,
421
+ "dateCreated":"2015-10-19T09:01:50",
422
+ "dateDeleted":null,
423
+ "dateModified":null
424
+ },
425
+ "id":664721,
426
+ "deleted":false,
427
+ "dateCreated":"0001-01-01T00:00:00",
428
+ "dateDeleted":null,
429
+ "dateModified":null
430
+ }
431
+ ],
432
+ "visibleAt":16500,
433
+ "visibleFor":null,
434
+ "shouldPause":1,
435
+ "interactiveId":183569,
436
+ "id":669510,
437
+ "deleted":false,
438
+ "dateCreated":"0001-01-01T00:00:00",
439
+ "dateDeleted":null,
440
+ "dateModified":null
441
+ }],
442
+ "immediateFeedback": true,
443
+ "id": 190303,
444
+ "name": "How Your Online Data is Used",
445
+ "duration": 0,
446
+ "posterImage": null,
447
+ "dateCreated": "2020-11-04T23:25:34",
448
+ "dateModified": null,
449
+ "dateDeleted": null,
450
+ "deleted": false
451
+ }
@@ -0,0 +1,62 @@
1
+ WEBVTT
2
+
3
+ 00:00:00.000 --> 00:00:01.000
4
+ /sample_1280x720.mp4.vtx#xywh=6,77,160,90
5
+
6
+ 00:00:01.000 --> 00:00:02.000
7
+ /sample_1280x720.mp4.vtx#xywh=171,77,160,90
8
+
9
+ 00:00:02.000 --> 00:00:04.000
10
+ /sample_1280x720.mp4.vtx#xywh=336,77,160,90
11
+
12
+ 00:00:04.000 --> 00:00:05.000
13
+ /sample_1280x720.mp4.vtx#xywh=501,77,160,90
14
+
15
+ 00:00:05.000 --> 00:00:06.000
16
+ /sample_1280x720.mp4.vtx#xywh=666,77,160,90
17
+
18
+ 00:00:06.000 --> 00:00:08.000
19
+ /sample_1280x720.mp4.vtx#xywh=6,172,160,90
20
+
21
+ 00:00:08.000 --> 00:00:09.000
22
+ /sample_1280x720.mp4.vtx#xywh=171,172,160,90
23
+
24
+ 00:00:09.000 --> 00:00:10.000
25
+ /sample_1280x720.mp4.vtx#xywh=336,172,160,90
26
+
27
+ 00:00:10.000 --> 00:00:12.000
28
+ /sample_1280x720.mp4.vtx#xywh=501,172,160,90
29
+
30
+ 00:00:12.000 --> 00:00:13.000
31
+ /sample_1280x720.mp4.vtx#xywh=666,172,160,90
32
+
33
+ 00:00:13.000 --> 00:00:14.000
34
+ /sample_1280x720.mp4.vtx#xywh=6,267,160,90
35
+
36
+ 00:00:14.000 --> 00:00:16.000
37
+ /sample_1280x720.mp4.vtx#xywh=171,267,160,90
38
+
39
+ 00:00:16.000 --> 00:00:17.000
40
+ /sample_1280x720.mp4.vtx#xywh=336,267,160,90
41
+
42
+ 00:00:17.000 --> 00:00:18.000
43
+ /sample_1280x720.mp4.vtx#xywh=501,267,160,90
44
+
45
+ 00:00:18.000 --> 00:00:20.000
46
+ /sample_1280x720.mp4.vtx#xywh=666,267,160,90
47
+
48
+ 00:00:20.000 --> 00:00:21.000
49
+ /sample_1280x720.mp4.vtx#xywh=6,362,160,90
50
+
51
+ 00:00:21.000 --> 00:00:22.000
52
+ /sample_1280x720.mp4.vtx#xywh=171,362,160,90
53
+
54
+ 00:00:22.000 --> 00:00:24.000
55
+ /sample_1280x720.mp4.vtx#xywh=336,362,160,90
56
+
57
+ 00:00:24.000 --> 00:00:25.000
58
+ /sample_1280x720.mp4.vtx#xywh=501,362,160,90
59
+
60
+ 00:00:25.000 --> 00:00:29.000
61
+ /sample_1280x720.mp4.vtx#xywh=666,362,160,90
62
+
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+
3
+ interface WrapProps {
4
+ el: HTMLElement;
5
+ }
6
+
7
+ export function Wrap(props: WrapProps): React.ReactElement {
8
+ const ref = React.useRef<HTMLDivElement>();
9
+
10
+ React.useEffect(() => {
11
+ if (ref.current)
12
+ ref.current.appendChild(props.el);
13
+ }, []);
14
+
15
+ return (
16
+ <div ref={ref}></div>
17
+ );
18
+ }
@@ -0,0 +1,238 @@
1
+ import { PlaybackObject, Files, Chapters, Subtitles, LocalCacheAddress } from '../../../src/interfaces/models/PlaybackObject';
2
+
3
+ import interactive from '../assets/interactive.json';
4
+
5
+ const basicFiles = <Files> {
6
+ progressive: [{
7
+ url: 'https://static.clickview.com.au/testvideos/vpsbenchmark.mp4',
8
+ mimeType: 'video/mp4',
9
+ profile: '720'
10
+ }]
11
+ };
12
+
13
+ const multipleFiles = <Files> {
14
+ progressive: [{
15
+ mimeType: 'video/mp4',
16
+ profile: '240',
17
+ url: 'https://static.clickview.com.au/testvideos/vpsbenchmark.mp4?quality=240'
18
+ },{
19
+ mimeType: 'video/mp4',
20
+ profile: '720',
21
+ url: 'https://static.clickview.com.au/testvideos/vpsbenchmark.mp4?quality=720'
22
+ }]
23
+ }
24
+
25
+ const emptyChapters = <Chapters> {
26
+ list: []
27
+ };
28
+
29
+ const mockChapters = <Chapters> {
30
+ list: [{
31
+ name: 'Waves',
32
+ timestamp: 0,
33
+ endTimestamp: 5
34
+ },
35
+ {
36
+ name: 'Some More Waves',
37
+ timestamp: 5,
38
+ endTimestamp: 11
39
+ },
40
+ {
41
+ name: 'Waves hitting Rocks',
42
+ timestamp: 11,
43
+ endTimestamp: 13
44
+ },
45
+ {
46
+ name: 'Rocky Rocks',
47
+ timestamp: 13,
48
+ endTimestamp: 20
49
+ },
50
+ {
51
+ name: 'End of waves',
52
+ timestamp: 20,
53
+ endTimestamp: 28
54
+ },
55
+ {
56
+ name: 'Credits',
57
+ timestamp: 28,
58
+ endTimestamp: 600
59
+ }]
60
+ };
61
+
62
+ const emptySubtitles = <Subtitles> {
63
+ list: []
64
+ };
65
+
66
+ const multipleSubtitles = <Subtitles> {
67
+ list: [{
68
+ label: 'English',
69
+ language: 'en',
70
+ url: './en.vtt',
71
+ mimeType: 'text/vtt'
72
+ }, {
73
+ label: 'Spanish',
74
+ language: 'es',
75
+ url: './es.vtt',
76
+ mimeType: 'text/vtt'
77
+ },{
78
+ label: 'French',
79
+ language: 'fr',
80
+ url: './fr.vtt',
81
+ mimeType: 'text/vtt'
82
+ }]
83
+ };
84
+
85
+ const emptyLocalCacheAddresses = <LocalCacheAddress[]> [];
86
+
87
+ const sources: { [key: string]: PlaybackObject } = {
88
+ basic: {
89
+ files: basicFiles,
90
+ mediaType: 'MasterVideo',
91
+ chapters: emptyChapters,
92
+ subtitles: {
93
+ list: [{
94
+ isDefault: false,
95
+ label: 'English',
96
+ language: 'en-au',
97
+ url: './en.vtt',
98
+ mimeType: 'text/vtt'
99
+ }]
100
+ },
101
+ localCacheAddresses: emptyLocalCacheAddresses
102
+ },
103
+ chapters: {
104
+ files: basicFiles,
105
+ mediaType: 'MasterVideo',
106
+ chapters: mockChapters,
107
+ subtitles: emptySubtitles,
108
+ localCacheAddresses: emptyLocalCacheAddresses
109
+ },
110
+ vertical: {
111
+ files: {
112
+ progressive: [{
113
+ mimeType: 'video/mp4',
114
+ profile: '720',
115
+ url: './vertical.mp4'
116
+ }]
117
+ },
118
+ mediaType: 'MasterVideo',
119
+ chapters: mockChapters,
120
+ subtitles: emptySubtitles,
121
+ localCacheAddresses: emptyLocalCacheAddresses
122
+ },
123
+ error: {
124
+ files: {
125
+ progressive: [{
126
+ mimeType: 'video/mp4',
127
+ profile: '240',
128
+
129
+ // This is intentionally not a video url
130
+ url: 'https://clickview.com.au'
131
+ }]
132
+ },
133
+ mediaType: 'MasterVideo',
134
+ chapters: emptyChapters,
135
+ subtitles: emptySubtitles,
136
+ localCacheAddresses: emptyLocalCacheAddresses
137
+ },
138
+ multipleQualities: {
139
+ files: multipleFiles,
140
+ mediaType: 'MasterVideo',
141
+ chapters: emptyChapters,
142
+ subtitles: emptySubtitles,
143
+ localCacheAddresses: emptyLocalCacheAddresses
144
+ },
145
+ poster: {
146
+ files: basicFiles,
147
+ mediaType: 'MasterVideo',
148
+ chapters: emptyChapters,
149
+ posterUrl: 'https://image.api.stg.cvnet.io/v2/thumbnails/Q229lo',
150
+ subtitles: emptySubtitles,
151
+ localCacheAddresses: emptyLocalCacheAddresses
152
+ },
153
+ everything: {
154
+ files: multipleFiles,
155
+ mediaType: 'MasterVideo',
156
+ chapters: mockChapters,
157
+ posterUrl: 'https://image.api.stg.cvnet.io/v2/thumbnails/Q229lo',
158
+ subtitles: multipleSubtitles,
159
+ localCacheAddresses: emptyLocalCacheAddresses
160
+ },
161
+ multipleSubtitles: {
162
+ files: basicFiles,
163
+ mediaType: 'MasterVideo',
164
+ chapters: emptyChapters,
165
+ subtitles: multipleSubtitles,
166
+ localCacheAddresses: emptyLocalCacheAddresses
167
+ },
168
+ multipleTracksWithDefault: {
169
+ files: basicFiles,
170
+ mediaType: 'MasterVideo',
171
+ chapters: emptyChapters,
172
+ subtitles: {
173
+ list: [{
174
+ label: 'English',
175
+ language: 'en',
176
+ url: './en.vtt',
177
+ mimeType: 'text/vtt'
178
+ }, {
179
+ label: 'Spanish',
180
+ language: 'es',
181
+ url: './es.vtt',
182
+ isDefault: true,
183
+ mimeType: 'text/vtt'
184
+ },{
185
+ label: 'French',
186
+ language: 'fr',
187
+ url: './fr.vtt',
188
+ mimeType: 'text/vtt'
189
+ }]
190
+ },
191
+ localCacheAddresses: emptyLocalCacheAddresses
192
+ },
193
+ interactive: {
194
+ files: basicFiles,
195
+ mediaType: 'MasterInteractive',
196
+ chapters: emptyChapters,
197
+ additionalData: interactive,
198
+ subtitles: emptySubtitles,
199
+ localCacheAddresses: emptyLocalCacheAddresses
200
+ },
201
+ clip: {
202
+ files: basicFiles,
203
+ mediaType: 'MasterClip',
204
+ chapters: emptyChapters,
205
+ additionalData: {
206
+ startTime: 60,
207
+ endTime: 240
208
+ },
209
+ subtitles: emptySubtitles,
210
+ localCacheAddresses: emptyLocalCacheAddresses
211
+ },
212
+ localcache: {
213
+ files: multipleFiles,
214
+ mediaType: 'MasterVideo',
215
+ chapters: emptyChapters,
216
+ subtitles: emptySubtitles,
217
+ localCacheAddresses: [{
218
+ httpsAddress: 'https://local.clickviewlocalcache.com:9055',
219
+ name: 'test',
220
+ playbackProfile: '1080'
221
+ }]
222
+ }
223
+ };
224
+
225
+ export function mockplayerApi(viewKey: string): Promise<PlaybackObject> {
226
+ const playbackObject = sources[viewKey];
227
+
228
+ if (!playbackObject)
229
+ throw Error('Unknown viewKey: ' + viewKey);
230
+
231
+ return new Promise<PlaybackObject>(resolve => {
232
+
233
+ // 200ms Timeout is just simulating an async api call to fetch tech info
234
+ window.setTimeout(() => {
235
+ resolve(playbackObject);
236
+ }, 200);
237
+ });
238
+ }
@@ -0,0 +1,16 @@
1
+ import { VideoJsPlayer } from 'video.js';
2
+ import { BasePlayerOptions } from '../../../src/players/base-player';
3
+
4
+ export const mockSetups: any = {
5
+ thumbnails: function(options: BasePlayerOptions, player: VideoJsPlayer): void {
6
+ player.addRemoteTextTrack({
7
+ kind: 'metadata',
8
+ src: './thumbnails.vtt',
9
+ label: 'thumbnails'
10
+ }, true);
11
+ },
12
+
13
+ everything: function(options: BasePlayerOptions, player: VideoJsPlayer): void {
14
+ mockSetups.thumbnails(options, player);
15
+ }
16
+ };
package/jest.config.js ADDED
@@ -0,0 +1,13 @@
1
+ const getBaseConfig = require('../../libs/testing/jest.base.config');
2
+ const { compilerOptions } = require('./tsconfig');
3
+
4
+ const paths = { ...compilerOptions.paths };
5
+
6
+ delete paths['*'];
7
+
8
+ const baseConfig = getBaseConfig(paths);
9
+
10
+ module.exports = {
11
+ ...baseConfig,
12
+ setupFilesAfterEnv: [...baseConfig.setupFilesAfterEnv, './test/setupTests.ts'],
13
+ };
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "@clickview/player",
3
- "version": "0.0.0-rc.4",
3
+ "version": "0.0.0",
4
4
  "description": "ClickView Player",
5
5
  "main": "dist/player-app.js",
6
6
  "scripts": {
7
7
  "start": "set NODE_ENV=development&& webpack -w --config ./tooling/webpack.config.js",
8
- "build": "set NODE_ENV=production&& webpack --config ./tooling/webpack.config.js",
8
+ "build": "set NODE_ENV=production&& webpack --config ./tooling/webpack.config.js",
9
+ "build-font": "set NODE_ENV=development&& npm run start",
9
10
  "dev-build": "set NODE_ENV=development&& webpack --config ./tooling/webpack.config.js",
10
- "dev-server": "set NODE_ENV=development&& webpack-dev-server --config ./tooling/webpack.config.js"
11
+ "storybook": "start-storybook -s ./.storybook/stories/assets -p 6006",
12
+ "build-storybook": "build-storybook",
13
+ "test": "jest",
14
+ "test-watch": "jest --watch",
15
+ "webpack-dev-server": "set NODE_ENV=development&& webpack-dev-server --config ./tooling/webpack.config.js"
11
16
  },
12
17
  "repository": {
13
18
  "type": "git",
@@ -20,13 +25,24 @@
20
25
  "author": "Matt Trengrove, Cameron Hill, Shale Kuzmanovski",
21
26
  "license": "ISC",
22
27
  "devDependencies": {
23
- "@clickview/tooling": "0.0.18",
28
+ "@babel/core": "7.11.6",
29
+ "@clickview/eslint-config": "1.0.0",
30
+ "@clickview/player-font": "0.0.1",
31
+ "@clickview/tooling": "0.0.19",
32
+ "@storybook/react": "6.0.26",
33
+ "@types/simplebar": "5.1.1",
24
34
  "@types/video.js": "7.3.11",
35
+ "jest": "26.1.0",
36
+ "ts-jest": "26.1.4",
25
37
  "webpack-dev-server": "3.11.0"
26
38
  },
27
39
  "dependencies": {
28
- "@clickview/styles": "1.0.9",
29
- "video.js": "7.8.4"
40
+ "@clickview/styles": "1.0.12",
41
+ "keycode": "2.2.0",
42
+ "simplebar": "5.3.0",
43
+ "simplebar-react": "2.3.0",
44
+ "video.js": "7.8.4",
45
+ "videojs-contrib-quality-levels": "2.0.9"
30
46
  },
31
47
  "babel": {
32
48
  "presets": [
@@ -0,0 +1,31 @@
1
+ import { VideoJsPlayer } from 'video.js';
2
+ import { BasePlayer, BasePlayerOptions } from '../../src/players/base-player';
3
+
4
+ export interface TestPlayerOptions extends BasePlayerOptions {
5
+ configurePlugins?: (player: VideoJsPlayer) => void;
6
+ }
7
+
8
+ export class TestPlayer extends BasePlayer {
9
+ protected options: TestPlayerOptions;
10
+
11
+ public player: VideoJsPlayer;
12
+
13
+ constructor(id: string | Element, options: TestPlayerOptions) {
14
+ super(id, options);
15
+ }
16
+
17
+ /**
18
+ * TODO: Look at reimplementing this. At the moment it's causing
19
+ * problems because the test will dispose of the player before the
20
+ * fetch has completed.
21
+ */
22
+ protected async fetch(): Promise<void> {
23
+ }
24
+
25
+ protected configurePlugins(): void {
26
+ if (!(typeof this.options.configurePlugins === 'function'))
27
+ return;
28
+
29
+ this.options.configurePlugins(this.player);
30
+ }
31
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Media playback isn't supported by the DOM provided by Jest
3
+ * so we mock the following functions.
4
+ */
5
+ window.HTMLMediaElement.prototype.load = () => { };
6
+ window.HTMLMediaElement.prototype.play = (() => { }) as any;
7
+ window.HTMLMediaElement.prototype.pause = () => { };
8
+ window.HTMLMediaElement.prototype.addTextTrack = (() => { }) as any;
@@ -0,0 +1,22 @@
1
+ import { TestPlayer, TestPlayerOptions } from '../players/test-player';
2
+
3
+ interface PlayerOptions extends Omit<TestPlayerOptions, 'viewKey'> { }
4
+
5
+ export const TestHelpers = {
6
+ makeEl(): HTMLElement {
7
+ const el = document.createElement('div');
8
+ el.id = 'test-player';
9
+ return el;
10
+ },
11
+
12
+ makePlayer(playerOptions?: PlayerOptions, containerEl?: HTMLElement): TestPlayer {
13
+ containerEl = containerEl || TestHelpers.makeEl();
14
+
15
+ document.body.innerHTML = '';
16
+ document.body.appendChild(containerEl);
17
+
18
+ playerOptions = playerOptions || {};
19
+
20
+ return new TestPlayer(containerEl, playerOptions as TestPlayerOptions);
21
+ }
22
+ };
@@ -0,0 +1,29 @@
1
+ import videojs from 'video.js';
2
+
3
+ declare module 'video.js' {
4
+ export interface VideoJsPlayer {
5
+ qualityLevels(): QualityLevelList;
6
+ }
7
+ }
8
+
9
+ declare module 'videojs-contrib-quality-levels' {
10
+ export interface QualityLevelList extends videojs.EventTarget {
11
+ [id: string]: QualityLevel;
12
+ selectedIndex: number;
13
+ length: number;
14
+ addQualityLevel(representation: Representation): QualityLevel;
15
+ removeQualityLevel(qualityLevel: QualityLevel): QualityLevel;
16
+ getQualityLevelById(id: string): QualityLevel;
17
+ }
18
+
19
+ export interface QualityLevel {
20
+ id: string;
21
+ label: string;
22
+ enabled: boolean;
23
+ }
24
+
25
+ export interface Representation {
26
+ id: string;
27
+ enabled(enabled: boolean): boolean;
28
+ }
29
+ }
package/dist/index.html DELETED
@@ -1,19 +0,0 @@
1
- <html>
2
-
3
- <head>
4
- <script src="./player-app.js"></script>
5
- <link rel="stylesheet" href="player-app.css" />
6
- </head>
7
-
8
- <body>
9
- <video id="my-player" class="video-js" data-setup='{"controls": true, "autoplay": true, "inactivityTimeout": 10000 }' width="600">
10
- <source src="https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4" type="video/mp4" />
11
- </video>
12
-
13
- <script>
14
- const CVPlayer = window['@clickview/player'];
15
- CVPlayer.CVPlayer.start('my-player');
16
- </script>
17
- </body>
18
-
19
- </html>