@kkcompany/player 2.9.20
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/CHANGELOG.md +1799 -0
- package/README.md +995 -0
- package/dist/core.mjs +8288 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +29038 -0
- package/dist/index.mjs +16650 -0
- package/dist/modules.d.ts +89 -0
- package/dist/modules.mjs +9095 -0
- package/dist/plugins.d.ts +5 -0
- package/dist/plugins.mjs +8062 -0
- package/dist/react.d.ts +178 -0
- package/dist/react.mjs +19176 -0
- package/package.json +393 -0
package/README.md
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
# Playcraft
|
|
2
|
+
|
|
3
|
+
Enjoy our latest update where we have fixed some bugs and improved our framework to provide you more stable playbacking experience.
|
|
4
|
+
|
|
5
|
+
Playcraft provides core player, premium player and premium+ player.
|
|
6
|
+
|
|
7
|
+
Playcraft also provides Google Cast Sender integration and mini controller UI.
|
|
8
|
+
|
|
9
|
+
## Developer Guide
|
|
10
|
+
|
|
11
|
+
We use husky v5 to handle git hooks.
|
|
12
|
+
|
|
13
|
+
Need to setup the husky environment **in first time**.
|
|
14
|
+
```
|
|
15
|
+
yarn install:husky
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Now, `commitlint` and `eslint` checking work well in every commit.
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
Install this package from git repository:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
yarn add @kkcompany/player
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
And install Shaka player:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
yarn add shaka-player
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Import, and compose `<Video>` component to your app:
|
|
35
|
+
|
|
36
|
+
```jsx
|
|
37
|
+
import React from 'react'
|
|
38
|
+
import {Video} from 'playcraft/react'
|
|
39
|
+
|
|
40
|
+
const MyApp = () => {
|
|
41
|
+
return (
|
|
42
|
+
<MyContainer>
|
|
43
|
+
<Video
|
|
44
|
+
source="https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd"
|
|
45
|
+
autoplay
|
|
46
|
+
/>
|
|
47
|
+
</MyContainer>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Legacy Browser Support
|
|
53
|
+
|
|
54
|
+
To deliver better experience, this package provides bundles with modern syntax for smaller bundle, but legacy browser is still compatible.
|
|
55
|
+
|
|
56
|
+
If your app is required to legacy browsers, make sure `@babel/preset-env` is configured correctly and polyfills are installed.
|
|
57
|
+
|
|
58
|
+
Currently polyfills may be required for these features :
|
|
59
|
+
|
|
60
|
+
- [ResizeObserver](https://github.com/juggle/resize-observer)
|
|
61
|
+
- `Array.prototype.at`
|
|
62
|
+
|
|
63
|
+
### Low Latency Live Mode
|
|
64
|
+
|
|
65
|
+
Low latency live supports Shaka player only, to opt-in low latency mode, ensure base player is Shaka and attach `latencyManager` to the player once loaded:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import {latencyManager} from 'playcraft/modules'
|
|
69
|
+
|
|
70
|
+
const MyLowLatencyLivePlayer = () => {
|
|
71
|
+
const videoRef = useRef()
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<PremiumPlayer
|
|
75
|
+
shaka
|
|
76
|
+
videoRef={videoRef}
|
|
77
|
+
onPlayerLoaded={player => {
|
|
78
|
+
latencyManager(player, videoRef.current).configure({enabled: true})
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## API Reference
|
|
86
|
+
|
|
87
|
+
**Sub bundles**
|
|
88
|
+
|
|
89
|
+
There's 4 sub bundles in this package, to provide player funcitons & components to different environments:
|
|
90
|
+
|
|
91
|
+
- `playcraft`: Original bundle for backward compatibility at this time, will provide core functions in future breaking/major versions
|
|
92
|
+
- `playcraft/core`: Core player functions(non-UI)
|
|
93
|
+
- `playcraft/react`: All React based components, make sure other sub bundles have no React dependency
|
|
94
|
+
- `playcraft/modules`: Utility functions to share across environments, such as Google Cast receivers
|
|
95
|
+
- `playcraft/plugins`: Plugins to share across environments
|
|
96
|
+
|
|
97
|
+
### `loadPlayer`
|
|
98
|
+
|
|
99
|
+
Load the player & return reference to player instance.
|
|
100
|
+
|
|
101
|
+
By default this loads Shaka player, you can specify `shaka` for Shaka player options:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import {loadPlayer} from 'playcraft/core'
|
|
105
|
+
|
|
106
|
+
const player = await loadPlayer(document.querySelector('video'), {
|
|
107
|
+
shaka: shakaOptions,
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Base player to load is determined by options, `loadPlayer(videoElement, {bitmovin: bitmovinOptions})` loads Bitmovin as base player.
|
|
112
|
+
|
|
113
|
+
### Plain JavaScript interface
|
|
114
|
+
|
|
115
|
+
While we can use the player object directly, this package also provides functions the works with all base players.
|
|
116
|
+
|
|
117
|
+
```js
|
|
118
|
+
import {subscribePlaybackState, load, playOrPause, seek} from 'playcraft/core'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `<Video>`
|
|
122
|
+
|
|
123
|
+
Import with: `import {Video} from 'playcraft/react'`.
|
|
124
|
+
|
|
125
|
+
Basic player component as a React component, a wrapper around base player.
|
|
126
|
+
|
|
127
|
+
This component renders `<video>` tag only, UI is not included.
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
<Video
|
|
133
|
+
source={[
|
|
134
|
+
{
|
|
135
|
+
type: 'dash',
|
|
136
|
+
src: 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd'
|
|
137
|
+
}
|
|
138
|
+
]}
|
|
139
|
+
autoplay
|
|
140
|
+
ref={videoRef}
|
|
141
|
+
|
|
142
|
+
playbackState="playing"
|
|
143
|
+
currentTime={123}
|
|
144
|
+
volume={0.8}
|
|
145
|
+
audio={audioTrackId}
|
|
146
|
+
subtitles={subTitleTrackId}
|
|
147
|
+
quality={{min: '720', max: '1080'}}
|
|
148
|
+
|
|
149
|
+
onPlaybackStateChange={(event, playbackState) => [event.type, playbackState]}
|
|
150
|
+
onTimeUpdate={() => videoRef.current.currentTime}
|
|
151
|
+
>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Source
|
|
155
|
+
|
|
156
|
+
An object or an array of objects containing `{type, src}`, URL to manifests of video and type of manifests.
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
;[
|
|
160
|
+
{
|
|
161
|
+
type: 'dash',
|
|
162
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: 'hls',
|
|
166
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
|
|
167
|
+
},
|
|
168
|
+
]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
In iOS browsers and MacOS Safari, the player chooses first HLS manifest and plays with built-in player provided by Apple.
|
|
172
|
+
|
|
173
|
+
In other browsers the player looks for first DASH manifest and plays with MediaSource Extensions.
|
|
174
|
+
|
|
175
|
+
**DRM**
|
|
176
|
+
|
|
177
|
+
To play content protection endabled videos, you should specify license server URLs and options in `source.drm`:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
;[
|
|
181
|
+
{
|
|
182
|
+
type: 'dash',
|
|
183
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
|
|
184
|
+
drm: {
|
|
185
|
+
widevine: 'https://drm.ex.com/portal',
|
|
186
|
+
playready: 'https://drm.ex.com/portal',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'hls',
|
|
191
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
|
|
192
|
+
drm: {
|
|
193
|
+
fairplay: {
|
|
194
|
+
licenseUri: 'https://drm.ex.com/portal',
|
|
195
|
+
certificateUri: 'https://drm.ex.com/portal/certificate',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### Props for Player Options
|
|
203
|
+
|
|
204
|
+
**`shaka`**
|
|
205
|
+
|
|
206
|
+
[Shaka player config](https://shaka-player-demo.appspot.com/docs/api/tutorial-config.html)
|
|
207
|
+
|
|
208
|
+
**`autoplay`**
|
|
209
|
+
|
|
210
|
+
Start playback when player component is mounted.
|
|
211
|
+
|
|
212
|
+
Defaults to `false`.
|
|
213
|
+
|
|
214
|
+
**`loop`**
|
|
215
|
+
|
|
216
|
+
Loop the current video. Check [HTMLMediaElement.loop](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-loop)
|
|
217
|
+
|
|
218
|
+
Defaults to `false`.
|
|
219
|
+
|
|
220
|
+
**`videoRef`**
|
|
221
|
+
|
|
222
|
+
Ref to html video element, use this for custom integrations.
|
|
223
|
+
|
|
224
|
+
**`playerRef`**
|
|
225
|
+
|
|
226
|
+
Ref to base player instance, use this for custom integrations.
|
|
227
|
+
|
|
228
|
+
#### Props for Playback
|
|
229
|
+
|
|
230
|
+
These props describe target state of playback, detailed design explaination can be found [here](hhttps://netflixtechblog.com/integrating-imperative-apis-into-a-react-application-1257e1b45ac6).
|
|
231
|
+
|
|
232
|
+
**`playbackState`**
|
|
233
|
+
|
|
234
|
+
Defines target state of the video, possible options are `playing` / `paused`.
|
|
235
|
+
|
|
236
|
+
Play button update with this prop immediately, for video playback, `paused` also takes effect instantly in most situations, but `playing` can only be applied when the video is ready to play.
|
|
237
|
+
|
|
238
|
+
Imperative `player.play()` is not recommended, since not all situations are safe to `play()`, requires extra checking or error handling, declarative prop `playbackState` handles these cases.
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const MyApp = () => {
|
|
244
|
+
const [playbackState, setPlaybackState] = useState('paused')
|
|
245
|
+
const play = () => {
|
|
246
|
+
setPlaybackState('playing')
|
|
247
|
+
}
|
|
248
|
+
const pause = () => {
|
|
249
|
+
setPlaybackState('paused')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<div>
|
|
254
|
+
<Video playbackState={playbackState} />
|
|
255
|
+
<button onClick={pause}>Pause</button>
|
|
256
|
+
<button onClick={play}>Play</button>
|
|
257
|
+
</div>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**`onPlaybackStateChange`**
|
|
263
|
+
|
|
264
|
+
Convenient event wrapper for playback state change.
|
|
265
|
+
|
|
266
|
+
States are: `loading`, `buffering`, `playing`, `paused`, `error`.
|
|
267
|
+
|
|
268
|
+
**`currentTime`**
|
|
269
|
+
|
|
270
|
+
Defines target time of the video, the video seeks to defined time when this prop is changed, no need to update this prop with playback time update, it only seeks when the prop updates.
|
|
271
|
+
|
|
272
|
+
User can also seek with seekbar when this props is set, whatever updates last takes effect.
|
|
273
|
+
|
|
274
|
+
**`onTimeUpdate`**
|
|
275
|
+
|
|
276
|
+
**`quality`**
|
|
277
|
+
|
|
278
|
+
Defines constraints on what streams/tracks should be selected in ABR, if nothing meets specified constriant, player fallbacks to base player decision.
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
{
|
|
282
|
+
minHeight: 480,
|
|
283
|
+
maxHeight: 1080,
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**`volume`**
|
|
288
|
+
|
|
289
|
+
Defines target volume of the video, the video updates to defined volume when this prop is changed.
|
|
290
|
+
|
|
291
|
+
If not specified, volume will be saved to local storage on change if available, and restore next time.
|
|
292
|
+
|
|
293
|
+
**`muted`**
|
|
294
|
+
|
|
295
|
+
Defines muted state of the video.
|
|
296
|
+
|
|
297
|
+
**`playbackRate`**
|
|
298
|
+
|
|
299
|
+
Defines target playback rate of the video, the video updates to defined rate when this prop is changed.
|
|
300
|
+
|
|
301
|
+
**`audioTrack`**
|
|
302
|
+
|
|
303
|
+
**`onPlayerLoaded`**
|
|
304
|
+
|
|
305
|
+
Called when the player is loaded.
|
|
306
|
+
|
|
307
|
+
**`onError`**
|
|
308
|
+
|
|
309
|
+
Called when an error is occurred.
|
|
310
|
+
|
|
311
|
+
`event.error.name` is typically source of the error, please refer to the documentation of base player, lookup with `event.error.code`.
|
|
312
|
+
|
|
313
|
+
**Other Props**
|
|
314
|
+
|
|
315
|
+
Additional props will be passed to video element.
|
|
316
|
+
|
|
317
|
+
### `<PremiumPlayer>`
|
|
318
|
+
|
|
319
|
+
Import with : `import {PremiumPlayer} from 'playcraft/react'`
|
|
320
|
+
|
|
321
|
+
**Example**
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
const [source, setSource] = useState([
|
|
325
|
+
{
|
|
326
|
+
type: 'dash',
|
|
327
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
type: 'hls',
|
|
331
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
|
|
332
|
+
}
|
|
333
|
+
], [])
|
|
334
|
+
|
|
335
|
+
<PremiumPlayer
|
|
336
|
+
source={source}
|
|
337
|
+
onChangeNext={() => {
|
|
338
|
+
// somthing like: setSource(nextSource)
|
|
339
|
+
}}
|
|
340
|
+
onChangePrevious={() => {}}
|
|
341
|
+
/>
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Source
|
|
345
|
+
|
|
346
|
+
`source` prop format is the same as `<Video>`, with some extensions.
|
|
347
|
+
|
|
348
|
+
**Thumbnails**
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
;[
|
|
352
|
+
{
|
|
353
|
+
type: 'dash',
|
|
354
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
type: 'thumbnail',
|
|
358
|
+
src: 'https://ex.com/thumbnails.vtt',
|
|
359
|
+
},
|
|
360
|
+
]
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Specify a source object with url to thumbnails data for thumbnail seeking feature.
|
|
364
|
+
|
|
365
|
+
**Quality**
|
|
366
|
+
|
|
367
|
+
The player gets the available qualities/profiles/resolutions/variants from the manifest as default and offer quality settings automatically, list of setting options can be overridden by specifying `source.qualityOptions`:
|
|
368
|
+
|
|
369
|
+
```js
|
|
370
|
+
{
|
|
371
|
+
type: 'dash',
|
|
372
|
+
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
|
|
373
|
+
qualityOptions: [
|
|
374
|
+
{label: '1080', value: 1080, options: {maxHeight: 1080}},
|
|
375
|
+
{label: '720', value: 720, options: {maxHeight: 720}},
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Props
|
|
381
|
+
|
|
382
|
+
All props of `<Video>` are supported.
|
|
383
|
+
|
|
384
|
+
**`autoplay`**
|
|
385
|
+
|
|
386
|
+
Whether the player starts playing after loading a source or not. Unmuted autoplay is blocked on major browsers, detect with `onAutoplayBlocked`.
|
|
387
|
+
|
|
388
|
+
Takes no effect when `playbackState` prop is given.
|
|
389
|
+
|
|
390
|
+
**`quality`**
|
|
391
|
+
|
|
392
|
+
When playing with Safari native HLS support, player can't set ABR constraints and quality selection is disabled.
|
|
393
|
+
|
|
394
|
+
To enable quality selection in Safari, specify `quality.rewriteManifest` to let player apply ABR constriants by manifest rewrite.
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
import {selectHlsQualities} from 'playcraft/modules'
|
|
398
|
+
;<PremiumPlayer
|
|
399
|
+
quality={{
|
|
400
|
+
rewriteManifest: selectHlsQualities,
|
|
401
|
+
}}
|
|
402
|
+
/>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
**`controls`**
|
|
406
|
+
|
|
407
|
+
Defines that player show UI or not.
|
|
408
|
+
Also support advanced option `autohide` object prop.
|
|
409
|
+
|
|
410
|
+
```js
|
|
411
|
+
<PremiumPlayer controls={true | false | autohide} />
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Use `autohide` with 3 seconds:
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
<PremiumPlayer controls={{autohide: 3000}} />
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
To show only title use `title-only`:
|
|
421
|
+
|
|
422
|
+
```js
|
|
423
|
+
<PremiumPlayer controls="title-only" />
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
To display a custom overlay, and temporarily disable built-in settings UI, use `no-panel`.
|
|
427
|
+
|
|
428
|
+
```js
|
|
429
|
+
<PremiumPlayer controls="no-panel" />
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Default value is `true`.
|
|
433
|
+
|
|
434
|
+
**`loop='disabled'`**
|
|
435
|
+
|
|
436
|
+
In addition to `true | false` for HTML video `loop`, you may also specify `'disabled'` to disable built-in loop menu item in settings UI.
|
|
437
|
+
|
|
438
|
+
**`chapters`**
|
|
439
|
+
|
|
440
|
+
Show chapters related UI: SeekBar and SideBar.
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
const chapters = [
|
|
444
|
+
{
|
|
445
|
+
startTime: 0,
|
|
446
|
+
displayName: 'OP',
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
startTime: 30,
|
|
450
|
+
displayName: 'Content',
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
startTime: 190,
|
|
454
|
+
displayName: 'Ending',
|
|
455
|
+
},
|
|
456
|
+
]
|
|
457
|
+
|
|
458
|
+
<PremiumPlayer chapters={chapters} />
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**`intl.locale`**
|
|
462
|
+
|
|
463
|
+
Language of settings UI & error messages.
|
|
464
|
+
|
|
465
|
+
**`intl.messages`**
|
|
466
|
+
|
|
467
|
+
Custom translations.
|
|
468
|
+
|
|
469
|
+
**`title`, `channelTitle`**
|
|
470
|
+
|
|
471
|
+
Video title text to be displayed at top.
|
|
472
|
+
|
|
473
|
+
**`onBack`**
|
|
474
|
+
|
|
475
|
+
A function that will be called when back button is clicked, back button is rendered at left of title, only if specified.
|
|
476
|
+
|
|
477
|
+
**`onChangeNext`, `onChangePrevious`**
|
|
478
|
+
|
|
479
|
+
Click handler for next / previous episode buttons.
|
|
480
|
+
If not specified, buttons will be disabled in mobile UI (or hidden in desktop UI).
|
|
481
|
+
|
|
482
|
+
**`onError`**
|
|
483
|
+
|
|
484
|
+
Premium player has built-in error UI, when an error is encountered, it stops playback and displays a overlay error message.
|
|
485
|
+
|
|
486
|
+
To opt-out error message for some specific error, use `event.preventDefault()`, you can unmount player component then re-mount it to restart silently.
|
|
487
|
+
|
|
488
|
+
Example:
|
|
489
|
+
|
|
490
|
+
```js
|
|
491
|
+
const MyVideoApp = () => {
|
|
492
|
+
const [playerSwitch, setPlayerSwitch] = useState('open')
|
|
493
|
+
const remount = () => {
|
|
494
|
+
flushSync(() => {
|
|
495
|
+
setPlayerSwitch('closed')
|
|
496
|
+
})
|
|
497
|
+
flushSync(() => {
|
|
498
|
+
setPlayerSwitch('open')
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return playerSwitch === 'open' && (
|
|
503
|
+
<PremiumPlayer
|
|
504
|
+
event => {
|
|
505
|
+
if (/PlayerError/.test(event.error.name) && event.error.code == 1000) {
|
|
506
|
+
event.preventDefault()
|
|
507
|
+
remount()
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
>
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**`modulesConfig`**
|
|
516
|
+
|
|
517
|
+
The common configuration for all modules.
|
|
518
|
+
Each module config is distinguished by the key prefix name:
|
|
519
|
+
```js
|
|
520
|
+
const modulesConfig = {
|
|
521
|
+
'moduleA.name': 'moduleA',
|
|
522
|
+
'moduleB.name': 'moduleB',
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
Currently, only support analytics module:
|
|
526
|
+
```js
|
|
527
|
+
const modulesConfig = {
|
|
528
|
+
'analytics.token': 'token',
|
|
529
|
+
'analytics.session_id': 'sessionId',
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
<PremiumPlayer
|
|
533
|
+
modulesConfig={modulesConfig}
|
|
534
|
+
/>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
**`onOpenSettings`**
|
|
538
|
+
|
|
539
|
+
Called when settings UI is open, by default, playback is paused when using mobile UI, you can opt-out with `event.preventDefault()`:
|
|
540
|
+
|
|
541
|
+
```js
|
|
542
|
+
<PremiumPlayer onOpenSettings={event => event.preventDefault()}>
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
**`slotProps`**
|
|
547
|
+
|
|
548
|
+
An object that contains the props for all slots within a component. You can use it to define additional custom props for a component's interior elements.
|
|
549
|
+
|
|
550
|
+
For example, the code snippet below shows how to make the volume control horizontal:
|
|
551
|
+
|
|
552
|
+
```js
|
|
553
|
+
<PremiumPlayer slotProps={{volumeControl: {slider: 'horizontal'}}}>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
#### Inner UI Components
|
|
557
|
+
|
|
558
|
+
List of component props available for `slots`, `slotProps` override.
|
|
559
|
+
|
|
560
|
+
**Seekbar**
|
|
561
|
+
|
|
562
|
+
In case you need custom seekbar, replace it by `slots.Seekbar` like a whole red bar for classic live UI:
|
|
563
|
+
|
|
564
|
+
```js
|
|
565
|
+
const Seekbar = () => (<div style={{height: '0.3em', background: 'red'}} />)
|
|
566
|
+
|
|
567
|
+
<PremiumPlayer slots={{
|
|
568
|
+
Seekbar,
|
|
569
|
+
}} />
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
**DisplayTime**
|
|
573
|
+
|
|
574
|
+
**VolumeControl**
|
|
575
|
+
|
|
576
|
+
- `slider`: `'vertical'(default) | 'horizontal'`
|
|
577
|
+
|
|
578
|
+
**Settings**
|
|
579
|
+
|
|
580
|
+
- `slots.root`: Root element of Settings UI, you may replace with `ClassicSettingsContainer` for classic settings UI look.
|
|
581
|
+
- `closeBy`: `'swipeDown'(default) | 'button'`
|
|
582
|
+
- `buttonPosition`: `'top-right' | 'bottom-right'`
|
|
583
|
+
|
|
584
|
+
```js
|
|
585
|
+
<PremiumPlayer slotProps={{
|
|
586
|
+
closeBy: 'button',
|
|
587
|
+
slots: {root: ClassicSettingsContainer}
|
|
588
|
+
}} />
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
#### UI Component Composition
|
|
592
|
+
|
|
593
|
+
**children**
|
|
594
|
+
|
|
595
|
+
All children will be rendered as children of player UI, use `position: absolute` to stack custom UI on player UI.
|
|
596
|
+
|
|
597
|
+
**<FunctionBarExtension>**
|
|
598
|
+
|
|
599
|
+
Import with : `import {FunctionBarExtension} from 'playcraft/react'`
|
|
600
|
+
|
|
601
|
+
In addition to children, you can also attach buttons or custom UI, at right of built-in buttons, or other specific places.
|
|
602
|
+
|
|
603
|
+
Internal layout component provides ref to the function bar container, the button is rendered at left of settings button, with React portal.
|
|
604
|
+
|
|
605
|
+
```js
|
|
606
|
+
<PremiumPlayer>
|
|
607
|
+
<FunctionBarExtension>
|
|
608
|
+
<MyCustomButton />
|
|
609
|
+
</FunctionBarExtension>
|
|
610
|
+
<MyOverlayUI />
|
|
611
|
+
</PremiumPlayer>
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**<TitleBarExtension>**
|
|
615
|
+
|
|
616
|
+
Add UI elements to the right or left of the title, `position` defaults to `'left'`.
|
|
617
|
+
|
|
618
|
+
Import with : `import {TitleBarExtension} from 'playcraft/react'`
|
|
619
|
+
|
|
620
|
+
You can add an empty container with a width of 1 ~ 3rem to shift the title position.
|
|
621
|
+
|
|
622
|
+
```js
|
|
623
|
+
<PremiumPlayer>
|
|
624
|
+
<TitleBarExtension position='left'>
|
|
625
|
+
<div style={{width: '2rem'}} />
|
|
626
|
+
</TitleBarExtension>
|
|
627
|
+
</PremiumPlayer>
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### `<PremiumPlusPlayer>`
|
|
631
|
+
|
|
632
|
+
Import with : `import {PremiumPlusPlayer} from 'playcraft/react'`
|
|
633
|
+
|
|
634
|
+
Premium+ player is premium player with integrated playback API support, and full set of enterprise features, good for fast OTT platform player integration.
|
|
635
|
+
|
|
636
|
+
All props of `<PremiumPlayer>` and `<Video>` are also available.
|
|
637
|
+
|
|
638
|
+
#### Props for enterprise features
|
|
639
|
+
|
|
640
|
+
**`preload`**
|
|
641
|
+
|
|
642
|
+
Default is 'auto', player starts playback session automatically.
|
|
643
|
+
If `none` is specified, player starts playback session when `load()` is called.
|
|
644
|
+
|
|
645
|
+
**`quality`**
|
|
646
|
+
|
|
647
|
+
In addition to `quality` prop supported in premium, `quality.getSettingOptions` is available to define quality setting options with resolution list provided by API.
|
|
648
|
+
|
|
649
|
+
```js
|
|
650
|
+
{
|
|
651
|
+
getSettingOptions: fixedQualityOptions // or abrLimitQualityOptions
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
**`uiElements`**
|
|
656
|
+
|
|
657
|
+
Use this props to overrides the default UI elements.
|
|
658
|
+
|
|
659
|
+
```jsx
|
|
660
|
+
// For hiding rewind/forward button
|
|
661
|
+
<PremiumPlusPlayer
|
|
662
|
+
uiElements={{
|
|
663
|
+
controlButtons: {
|
|
664
|
+
rewindButton: false,
|
|
665
|
+
forwardButton: false
|
|
666
|
+
}
|
|
667
|
+
}}
|
|
668
|
+
/>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### Followings are the configurable UI elements:
|
|
672
|
+
|
|
673
|
+
```
|
|
674
|
+
{
|
|
675
|
+
controlButtons: {
|
|
676
|
+
playButton
|
|
677
|
+
rewindButton
|
|
678
|
+
forwardButton
|
|
679
|
+
nextEpisodeButton
|
|
680
|
+
previousEpisodeButton
|
|
681
|
+
}
|
|
682
|
+
seekbar
|
|
683
|
+
backButton
|
|
684
|
+
fullscreenButton
|
|
685
|
+
volumeControl
|
|
686
|
+
backItems
|
|
687
|
+
liveButton,
|
|
688
|
+
castButton,
|
|
689
|
+
settingButton
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
#### Props for playback API
|
|
694
|
+
|
|
695
|
+
Updating `host`, `accessToken`, `deviceId`, `headers` or `params` doesn't restart existing playback session, sub subseqent requests will be send with new prop values.
|
|
696
|
+
|
|
697
|
+
To restart a session, please update `contentKey` instead.
|
|
698
|
+
|
|
699
|
+
**`host`**
|
|
700
|
+
|
|
701
|
+
URL of playback API.
|
|
702
|
+
|
|
703
|
+
**`accessToken`**
|
|
704
|
+
|
|
705
|
+
Access token of current user, this is optional if access control is not needed.
|
|
706
|
+
|
|
707
|
+
This will be added to header `Authorization` of playback API requests, and headers of DRM portal requests.
|
|
708
|
+
|
|
709
|
+
**`deviceId`**
|
|
710
|
+
|
|
711
|
+
Unique identifier of current device, needed for concurrent device count limit.
|
|
712
|
+
|
|
713
|
+
**`headers`**
|
|
714
|
+
|
|
715
|
+
Additional headers for playback API requests.
|
|
716
|
+
|
|
717
|
+
**`params`**
|
|
718
|
+
|
|
719
|
+
Additional query parameters for playback API requests.
|
|
720
|
+
|
|
721
|
+
**`contentType`, `contentId`**
|
|
722
|
+
|
|
723
|
+
Content to request from playback API, types are `videos` / `lives`.
|
|
724
|
+
|
|
725
|
+
**`contentKey`**
|
|
726
|
+
|
|
727
|
+
By default API requests are cached with key `${contentType}/${contentId}`, when the backend provides different by `headers` or `queries`, you can also provide custom key for cache.
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
<PremiumPlusPlayer
|
|
731
|
+
contentKey="linear/1"
|
|
732
|
+
contentType="live"
|
|
733
|
+
contentId="1"
|
|
734
|
+
preloadList={[
|
|
735
|
+
params={{playback_type: 'linear'}}
|
|
736
|
+
preloadList{[
|
|
737
|
+
{contentType: 'lives', contentId: '1', contentKey: 'whatever/1', params: {playback_type: 'whatever'},
|
|
738
|
+
{contentType: 'lives', contentId: '2', contentKey: 'linear/2', params: {playback_type: 'linear'},
|
|
739
|
+
]}
|
|
740
|
+
]}
|
|
741
|
+
/>
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
**`preloadList`**
|
|
745
|
+
|
|
746
|
+
A list of content to pre-request the playback info and the content data. The data format should be like:
|
|
747
|
+
|
|
748
|
+
```ts
|
|
749
|
+
type PreloadList = {
|
|
750
|
+
contentId
|
|
751
|
+
contentType
|
|
752
|
+
}[]
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Note that the array reference should keep the same if the content doesn't change. We suggest using `useMemo` to wrap the `preloadList`:
|
|
756
|
+
|
|
757
|
+
```js
|
|
758
|
+
const preloadList = useMemo(
|
|
759
|
+
() => [
|
|
760
|
+
{contentId: '1', contentType: 'videos'},
|
|
761
|
+
{contentId: '2', contentType: 'videos'},
|
|
762
|
+
],
|
|
763
|
+
[]
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
return <PremiumPlusPlayer preloadList={preloadList} />
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**`onApiError(error, {retry, retryTimes})`**
|
|
770
|
+
|
|
771
|
+
Handler for API request errors, `error` is error object of axios, you may resend request with `retry`.
|
|
772
|
+
|
|
773
|
+
Default behavior is:
|
|
774
|
+
|
|
775
|
+
- For temporarily server or network error, wait 3 seconds and retry 3 times
|
|
776
|
+
- For critical API `/start` and `/info`, pass error back
|
|
777
|
+
- Ignore errors for other API errors by return a pending promise
|
|
778
|
+
|
|
779
|
+
Example:
|
|
780
|
+
|
|
781
|
+
```js
|
|
782
|
+
const ignoreMinorError = async (error, {retry, retryTimes} = {}) => {
|
|
783
|
+
if (
|
|
784
|
+
(error.response.message === 'Network Error' ||
|
|
785
|
+
/502|503/.test(error.response.status)) &&
|
|
786
|
+
retryTimes < 3
|
|
787
|
+
) {
|
|
788
|
+
await waitMs(3000)
|
|
789
|
+
return retry()
|
|
790
|
+
}
|
|
791
|
+
if (/start$|info$/.test(error.config.url)) {
|
|
792
|
+
return Promise.reject(error)
|
|
793
|
+
}
|
|
794
|
+
console.log('Ignore non-critical playback API fail', error)
|
|
795
|
+
return new Promise(() => {})
|
|
796
|
+
}
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Plugins
|
|
800
|
+
|
|
801
|
+
Import from `playcraft/plugins`.
|
|
802
|
+
|
|
803
|
+
While this library provides common features, some of features are not required in all use cases, these features are implemented as plguins, to make app package dependencies clean,
|
|
804
|
+
and bundle size won't increace with unused features.
|
|
805
|
+
|
|
806
|
+
⚠️ When using plugins with React UI, make sure the plugins are stored with a reference and
|
|
807
|
+
are not initialized on re-render(see React example below).
|
|
808
|
+
|
|
809
|
+
Since main bundle is not [side-effect-free](https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free) yet, plugins are in sub bundle `playcraft/plugins`.
|
|
810
|
+
|
|
811
|
+
#### `MediaTailorPlugin`
|
|
812
|
+
|
|
813
|
+
This plugin loads streams with server-side stitched ad from MediaTailor, and provides ad related functionalies.
|
|
814
|
+
|
|
815
|
+
Ad UI is not included in this plugin.
|
|
816
|
+
|
|
817
|
+
**`adParams`**
|
|
818
|
+
|
|
819
|
+
⚠️ Warning: this prop is experimental.
|
|
820
|
+
|
|
821
|
+
Set personalized ads for MediaTailor.
|
|
822
|
+
|
|
823
|
+
This props should be inserted with `MediaTailorPlugin` contructor.
|
|
824
|
+
|
|
825
|
+
Default is empty JSON.
|
|
826
|
+
|
|
827
|
+
```jsx
|
|
828
|
+
const adParams = {user: 'tim'}
|
|
829
|
+
const plugin = MediaTailorPlugin({adParams})
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
**Features**
|
|
833
|
+
|
|
834
|
+
- Load ad stitched streams from MediaTailor
|
|
835
|
+
- Load ad tracking event data & send tracking events(beacons)
|
|
836
|
+
- Snapback
|
|
837
|
+
- Provide playback time of original content
|
|
838
|
+
- Provide ad playback status
|
|
839
|
+
- Provide ad events
|
|
840
|
+
- Provide skip ad function
|
|
841
|
+
|
|
842
|
+
**Example for React**
|
|
843
|
+
|
|
844
|
+
To avoid re-initializing plugins on re-render, please wrap it with useMemo.
|
|
845
|
+
|
|
846
|
+
```js
|
|
847
|
+
import {Player} from 'playcraft'
|
|
848
|
+
import {MediaTailorPlugin} from 'playcraft/plugins'
|
|
849
|
+
|
|
850
|
+
const MyPlayerView = () => {
|
|
851
|
+
const plugins = useMemo(() => [MediaTailorPlugin()], [])
|
|
852
|
+
|
|
853
|
+
return (
|
|
854
|
+
<MyContainer>
|
|
855
|
+
<Player plugins={plugins} />
|
|
856
|
+
</MyContainer>
|
|
857
|
+
)
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
**Example for Cast receiver**
|
|
862
|
+
|
|
863
|
+
This plugin can also integrate with Playcraft Cast receiver.
|
|
864
|
+
|
|
865
|
+
```js
|
|
866
|
+
import {MediaTailorPlugin} from 'playcraft/plugins'
|
|
867
|
+
import {castService} from 'playcraft-google-cast'
|
|
868
|
+
|
|
869
|
+
castService.start({
|
|
870
|
+
plugins: [MediaTailorPlugin()],
|
|
871
|
+
})
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
#### `ImaDaiPlugin`
|
|
875
|
+
|
|
876
|
+
This plugin enable the playcraft integration with [ImaDai SDK for HTML5](https://developers.google.com/interactive-media-ads/docs/sdks/html5/dai).
|
|
877
|
+
You can use it as follow:
|
|
878
|
+
|
|
879
|
+
```jsx
|
|
880
|
+
import {ImaDaiPlugin} from 'playcraft/plugins'
|
|
881
|
+
|
|
882
|
+
function ContainerComponent(props) {
|
|
883
|
+
const plugins = useMemo(() => [ImaDaiPlugin()], [])
|
|
884
|
+
return <PremiumPlusPlayer plugins={plugins} />
|
|
885
|
+
}
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
**`requestOptionOverrides`**
|
|
889
|
+
|
|
890
|
+
Accept an object to overrides the default [StreamRequest](https://developers.google.com/interactive-media-ads/docs/sdks/html5/dai/reference/js/StreamRequest):
|
|
891
|
+
|
|
892
|
+
```jsx
|
|
893
|
+
const plugins = useMemo(
|
|
894
|
+
() => [
|
|
895
|
+
ImaDaiPlugin({
|
|
896
|
+
requestOptionsOverrides: {adTagParameters: {tfcd: 1}},
|
|
897
|
+
}),
|
|
898
|
+
],
|
|
899
|
+
[]
|
|
900
|
+
)
|
|
901
|
+
return <PremiumPlusPlayer plugins={plugins} />
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Modules
|
|
905
|
+
|
|
906
|
+
Import with: `import {mapLogEvents} from 'playcraft/modules`
|
|
907
|
+
|
|
908
|
+
This sub bundle contains building blocks unplugged from enterprise player, for crafting a player from the super flexible minimal player or other players.
|
|
909
|
+
|
|
910
|
+
#### `mapLogEvents`
|
|
911
|
+
|
|
912
|
+
A observer operator-like funciton, take video element, generates **playback log events** to be sent to amplitude.
|
|
913
|
+
|
|
914
|
+
Cast receiver also handle playlog with this function.
|
|
915
|
+
|
|
916
|
+
Premium+ already integrated playlog in it, no need to use this function.
|
|
917
|
+
|
|
918
|
+
For premium player or other players, simply pass video element and pass additional events with `logTarget.emit`:
|
|
919
|
+
|
|
920
|
+
```js
|
|
921
|
+
const MyApp = () => {
|
|
922
|
+
const videoRef = useRef()
|
|
923
|
+
const logTarget = useRef()
|
|
924
|
+
useEffect(() => {
|
|
925
|
+
logTarget.current = mapLogEvents({
|
|
926
|
+
playerName: 'shaka',
|
|
927
|
+
version: process.env.VERSION,
|
|
928
|
+
video: videoRef.current,
|
|
929
|
+
})
|
|
930
|
+
}, [])
|
|
931
|
+
|
|
932
|
+
return (
|
|
933
|
+
<PremiumPlayer
|
|
934
|
+
videoRef={videoRef}
|
|
935
|
+
sendLog={(name, data) => logTarget.current.emit(name, data)}
|
|
936
|
+
/>
|
|
937
|
+
)
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### Google Cast Sender Integration
|
|
942
|
+
|
|
943
|
+
For plain JS integration, sender integration & mini control UI comes with player UI automatically, just specify `castReceiverAppId` and it will handle the rest, including initialization of Google Cast sender SDK.
|
|
944
|
+
|
|
945
|
+
#### `<CastSender>`
|
|
946
|
+
|
|
947
|
+
For React integration, enable Cast by adding `<CastSender>` to your app.
|
|
948
|
+
|
|
949
|
+
`<PremiumPlayer>` have a Cast button, which will display automatically when Google Cast sender is initialized and there's some available receiver ready for connection.
|
|
950
|
+
|
|
951
|
+
```js
|
|
952
|
+
import {CastSender} from 'playcraft/react'
|
|
953
|
+
|
|
954
|
+
<CastSender
|
|
955
|
+
appId="XXXXXX"
|
|
956
|
+
intl={{locale: 'ja'}}
|
|
957
|
+
/>
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
To add `customData` for your own custom logic, use `onCast` handler of `<PremiumPlayer>`:
|
|
961
|
+
|
|
962
|
+
```js
|
|
963
|
+
<PremiumPlayer onCast={async request => {
|
|
964
|
+
request.currentTime = 123
|
|
965
|
+
request.media.title = 'My Title'
|
|
966
|
+
Object.assign(
|
|
967
|
+
request.media.customData,
|
|
968
|
+
await fetchMyCastCustomData()
|
|
969
|
+
)
|
|
970
|
+
}}>
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
## Workarounds
|
|
974
|
+
|
|
975
|
+
### Pause when Unplugging Headphones in iOS
|
|
976
|
+
|
|
977
|
+
Mobile web video will be paused by OS when unplugging headphones, but in some iOS versions, video is paused without an event, and cause UI state inconsistent.
|
|
978
|
+
|
|
979
|
+
A function `handleIOSHeadphonesDisconnection` is provided to workaround this.
|
|
980
|
+
|
|
981
|
+
**Example**
|
|
982
|
+
|
|
983
|
+
```js
|
|
984
|
+
import React, {useEffect} from 'react'
|
|
985
|
+
import {Player} from 'playcraft'
|
|
986
|
+
import {handleIOSHeadphonesDisconnection} from 'playcraft/modules'
|
|
987
|
+
|
|
988
|
+
const MyVideoComponent = () => {
|
|
989
|
+
useEffect(() => {
|
|
990
|
+
handleIOSHeadphonesDisconnection()
|
|
991
|
+
}, [])
|
|
992
|
+
|
|
993
|
+
return <Player />
|
|
994
|
+
}
|
|
995
|
+
```
|