Enjoy our latest update where we have fixed some bugs and improved our framework to provide you more stable playbacking experience.
Playcraft provides core player, premium player and premium+ player.
Playcraft also provides Google Cast Sender integration and mini controller UI.
Playcraft supports multiple base web players, currently Shaka and Bitmovin are supported.
Install this package from git repository:
yarn add https://github.com/KKStream/playcraft-web-sdk#v1.9.0
And install Shaka player:
yarn add shaka-player
Import, and compose <Video>
component to your app:
import React from 'react'
import {Video} from 'playcraft/react'
const MyApp = () => {
return (
<MyContainer>
<Video
source="https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd"
autoplay
/>
</MyContainer>
)
}
To deliver better experience, this package provides bundles with modern syntax for smaller bundle, but legacy browser is still compatible.
If your app is required to legacy browsers, make sure @babel/preset-env
is configured correctly and polyfills are installed.
Currently polyfills may be required for these features :
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:
import {latencyManager} from 'playcraft/modules'
const MyLowLatencyLivePlayer = () => {
const videoRef = useRef()
return (
<PremiumPlayer
shaka
videoRef={videoRef}
onPlayerLoaded={player => {
latencyManager(player, videoRef.current).configure({enabled: true})
}}
/>
)
}
Sub bundles
There's 4 sub bundles in this package, to provide player funcitons & components to different environments:
playcraft
: Original bundle for backward compatibility at this time, will provide core functions in future breaking/major versionsplaycraft/core
: Core player functions(non-UI)playcraft/react
: All React based components, make sure other sub bundles have no React dependencyplaycraft/modules
: Utility functions to share across environments, such as Google Cast receiversplaycraft/plugins
: Plugins to share across environments
Load the player & return reference to player instance.
By default this loads Shaka player, you can specify shaka
for Shaka player options:
import {loadPlayer} from 'playcraft/core'
const player = await loadPlayer(document.querySelector('video'), {
shaka: shakaOptions,
})
Base player to load is determined by options, loadPlayer(videoElement, {bitmovin: bitmovinOptions})
loads Bitmovin as base player.
For Bitmovin, player with necessary modules based on current browser.
Reference of Bitmovin player: https://bitmovin.com/docs/player/api-reference/web/web-sdk-api-reference-v8#/player/web/8/docs/interfaces/core.playerapi.html
While we can use the player object directly, this package also provides functions the works with all base players.
import {subscribeMediaState, load, playOrPause, seek} from 'playcraft/core'
Import with: import {Video} from 'playcraft/react'
.
Basic player component as a React component, a wrapper around base player.
This component renders <video>
tag only, UI is not included.
Example:
<Video
source={[
{
type: 'dash',
src: 'https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd'
}
]}
autoplay
shaka
ref={videoRef}
playbackState="playing"
currentTime={123}
volume={0.8}
audio={audioTrackId}
subtitles={subTitleTrackId}
quality={{min: '720', max: '1080'}}
onPlaybackStateChange={(event, playbackState) => [event.type, playbackState]}
onTimeUpdate={() => videoRef.current.currentTime}
>
An object or an array of objects containing {type, src}
, URL to manifests of video and type of manifests.
;[
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
},
{
type: 'hls',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
},
]
In iOS browsers and MacOS Safari, the player chooses first HLS manifest and plays with built-in player provided by Apple.
In other browsers the player looks for first DASH manifest and plays with MediaSource Extensions.
DRM
To play content protection endabled videos, you should specify license server URLs and options in source.rm
:
;[
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
drm: {
widevine: 'https://drm.ex.com/portal',
playready: 'https://drm.ex.com/portal',
},
},
{
type: 'hls',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
drm: {
fairplay: {
licenseUri: 'https://drm.ex.com/portal',
certificateUri: 'https://drm.ex.com/portal/certificate',
},
},
},
]
source.drm.widevine.level
In most situations, there's no need to set this, for best compatibility.
Actual robustness/security level is determined by the browser, it may use L3 even if L1 works well.
It is recommended that a robustness level be specified. Not specifying the robustness level could result in unexpected behavior, potentially including failure to play.
This message is safe to ignore, in case Chrome complaining in the console.
In case hardware based content protection(L1) is required, please set HW_SECURE_DECODE
.
If you have a list of devices that doesn't plays well with L1, please set SW_SECURE_DECODE
to force L3.
For advanced or experimental usage, the possible value is the one of SW_SECURE_CRYPTO
, SW_SECURE_DECODE
, HW_SECURE_CRYPTO
, HW_SECURE_DECODE
, HW_SECURE_ALL
, this is Widevine specific and not supported in other key systems.
Base player to load is determined by prop, available base players are shaka
and bitmovin
.
Example:
<Video shaka /> // load Shaka player with default config
<Video shaka={myShakaConfig} /> // override some Shaka config
<Video bitmovin={{{key: 'my-license-key'}}} /> // Load Bitmovin with config override
Currently changing base player is not supported, please un-mount and remount player component.
shaka
bitmovin
Bitmovin player will be loaded if this prop is specified.
key
is required if not running in localhost.
autoplay
Start playback when player component is mounted.
Defaults to false
.
loop
Loop the current video. Check HTMLMediaElement.loop
Defaults to false
.
videoRef
Ref to html video element, use this for custom integrations.
playerRef
Ref to base player instance, use this for custom integrations.
These props describe target state of playback, detailed design explaination can be found here.
playbackState
Defines target state of the video, possible options are playing
/ paused
.
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.
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.
Example:
const MyApp = () => {
const [playbackState, setPlaybackState] = useState('paused')
const play = () => {
setPlaybackState('playing')
}
const pause = () => {
setPlaybackState('paused')
}
return (
<div>
<Video playbackState={playbackState} />
<button onClick={pause}>Pause</button>
<button onClick={play}>Play</button>
</div>
)
}
onPlaybackStateChange
Convenient event wrapper for playback state change.
States are: loading
, buffering
, playing
, paused
, error
.
currentTime
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.
User can also seek with seekbar when this props is set, whatever updates last takes effect.
onTimeUpdate
quality
Defines constraints on what streams/tracks should be selected in ABR, if nothing meets specified constriant, player fallbacks to base player decision.
{
minHeight: 480,
maxHeight: 1080,
}
volume
Defines target volume of the video, the video updates to defined volume when this prop is changed.
muted
Defines muted state of the video.
playbackRate
Defines target playback rate of the video, the video updates to defined rate when this prop is changed.
audioTrack
onPlayerLoaded
Called when the player is loaded.
onError
Called when an error is occurred.
event.error.name
is typically source of the error, please refer to the documentation of base player, lookup with event.error.code
.
Other Props
Additional props will be passed to video element.
Import with: import {VideoPlayer} from 'playcraft/react'
.
Basic player component as a React component, a wrapper around base player.
This renders video with basic player UI.
Example:
<VideoPlayer
source="https://dash.akamaized.net/dash264/TestCases/1a/sony/SNE_DASH_SD_CASE1A_REVISED.mpd"
autoplay
shaka
/>
All props of <Video>
are supported
style
Style of top level element of this component.
autohide
Autohide player UI after no UI interaction after 3 seconds.
Defaults to false
.
Import with : import {PremiumPlayer} from 'playcraft/react'
Example
const [source, setSource] = useState([
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
},
{
type: 'hls',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths-hls/hls.m3u8',
}
], [])
<PremiumPlayer
source={source}
onChangeNext={() => {
// somthing like: setSource(nextSource)
}}
onChangePrevious={() => {}}
/>
source
prop format is the same as <Video>
, with some extensions.
Thumbnails
;[
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
},
{
type: 'thumbnail',
src: 'https://ex.com/thumbnails.vtt',
},
]
Specify a source object with url to thumbnails data for thumbnail seeking feature.
Quality
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
:
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
qualityOptions: [
{label: '1080', value: 1080, options: {maxHeight: 1080}},
{label: '720', value: 720, options: {maxHeight: 720}},
],
},
All props of <Video>
are supported.
autoplay
Whether the player starts playing after loading a source or not. Unmuted autoplay is blocked on major browsers, detect with onAutoplayBlocked
.
Takes no effect when playbackState
prop is given.
quality
When playing with Safari native HLS support, player can't set ABR constraints and quality selection is disabled.
To enable quality selection in Safari, specify quality.rewriteManifest
to let player apply ABR constriants by manifest rewrite.
import {selectHlsQualities} from 'playcraft/modules'
;<PremiumPlayer
quality={{
rewriteManifest: selectHlsQualities,
}}
/>
controls
Defines that player show UI or not.
Also support advanced option autohide
object prop.
<PremiumPlayer controls={true | false | autohide} />
Use autohide
with 3 seconds:
<PremiumPlayer controls={{autohide: 3000}} />
To show only title use title-only
:
<PremiumPlayer controls="title-only" />
To display a custom overlay, and temporarily disable built-in settings UI, use no-panel
.
<PremiumPlayer controls="no-panel" />
Default value is true
.
intl.locale
Language of settings UI & error messages.s
intl.messages
Custom translations.
title
, channelTitle
Video title text to be displayed at top.
subtitleTrack
onBack
A function that will be called when back button is clicked, back button is rendered at left of title, only if specified.
onChangeNext
, onChangePrevious
Click handler for next / previous episode buttons. If not specified, buttons will be disabled in mobile UI (or hidden in desktop UI).
onError
Premium player has built-in error UI, when an error is encountered, it stops playback and displays a overlay error message.
To opt-out error message for some specific error, use event.preventDefault()
, you can unmount player component then re-mount it to restart silently.
Example:
const MyVideoApp = () => {
const [playerSwitch, setPlayerSwitch] = useState('open')
const remount = () => {
flushSync(() => {
setPlayerSwitch('closed')
})
flushSync(() => {
setPlayerSwitch('open')
})
}
return playerSwitch === 'open' && (
<PremiumPlayer
event => {
if (/PlayerError/.test(event.error.name) && event.error.code == 1000) {
event.preventDefault()
remount()
}
}
>
)
}
sendLog(eventName, {currentTime})
Optional log function for playback / UI events.
Events are subject to change:
- playing, paused
- rewind, forward
- previousEpisode, nextEpisode
- openSettings, closeSettings
This prop is mainly for behaviors can only be observed inside premium player, for amplitude and log
services integration, see mapLogEvents
.
Playback properties are provided as an object, you can also add custom properties.
Example
sendLog={(eventName, properties) => {
sendToLogService(eventName, {
...properties,
...myCustomProperties,
})
}}
onOpenSettings
Called when settings UI is open, by default, playback is paused when using mobile UI, you can opt-out with event.preventDefault()
:
<PremiumPlayer onOpenSettings={event => event.preventDefault()}>
uiElements
Use this props to overrides the default UI elements. See more about uiElements configuration.
Use fullscreenButton for hiding full-screen button.
<PremiumPlayer uiElements={{fullscreenButton: false}}>
children
All children will be rendered as children of player UI, use position: absolute
to stack custom UI on player UI.
Import with : import {FunctionBarExtension} from 'playcraft/react'
In addition to children, you can also attach buttons or custom UI, at right of built-in buttons, or other specific places.
Internal layout component provides ref to the function bar container, the button is rendered at left of settings button, with React portal.
<PremiumPlayer>
<FunctionBarExtension>
<MyCustomButton />
</FunctionBarExtension>
<MyOverlayUI />
</PremiumPlayer>
Import with : import {TitleBarExtension} from 'playcraft/react'
You can add an empty container with a width of 1 ~ 3rem to shift the title position.
<PremiumPlayer>
<TitleBarExtension>
<div style={{width: '2rem'}} />
</TitleBarExtension>
</PremiumPlayer>
Import with : import {PremiumPlusPlayer} from 'playcraft/react'
Premium+ player is premium player with integrated playback API support, and full set of enterprise features, good for fast OTT platform player integration.
All props of <PremiumPlayer>
and <Video>
are also available.
preload
Default is 'auto', player starts playback session automatically.
If none
is specified, player starts playback session when load()
is called.
quality
In addition to quality
prop supported in premium, quality.getSettingOptions
is available to define quality setting options with resolution list provided by API.
{
getSettingOptions: fixedQualityOptions // or abrLimitQualityOptions
}
uiElements
Use this props to overrides the default UI elements.
// For hiding rewind/forward button
<PremiumPlusPlayer
uiElements={{
controlButtons: {
rewindButton: false,
forwardButton: false
}
}}
/>
{
controlButtons: {
playButton
rewindButton
forwardButton
nextEpisodeButton
previousEpisodeButton
}
seekbar
backButton
fullscreenButton
volumeControl
backItems
liveButton,
castButton,
settingButton
}
host
URL of playback API.
accessToken
Access token of current user, this is optional if access control is not needed.
This will be added to header Authorization
of playback API requests, and headers of DRM portal requests.
deviceId
Unique identifier of current device, needed for concurrent device count limit.
headers
Additional headers for playback API requests.
params
Additional query parameters for playback API requests.
contentType
, contentId
Content to request from playback API, types are videos
/ lives
.
preloadList
A list of content to pre-request the playback info and the content data. The data format should be like:
type PreloadList = {
contentId
contentType
}[]
Note that the array reference should keep the same if the content doesn't change. We suggest using useMemo
to wrap the preloadList
:
const preloadList = useMemo(
() => [
{contentId: '1', contentType: 'videos'},
{contentId: '2', contentType: 'videos'},
],
[]
)
return <PremiumPlusPlayer preloadList={preloadList} />
onApiError(error, {retry, retryTimes})
Handler for API request errors, error
is error object of axios, you may resend request with retry
.
Default behavior is:
- For temporarily server or network error, wait 3 seconds and retry 3 times
- For critical API
/start
and/info
, pass error back - Ignore errors for other API errors by return a pending promise
Example:
const ignoreMinorError = async (error, {retry, retryTimes} = {}) => {
if (
(error.response.message === 'Network Error' ||
/502|503/.test(error.response.status)) &&
retryTimes < 3
) {
await waitMs(3000)
return retry()
}
if (/start$|info$/.test(error.config.url)) {
return Promise.reject(error)
}
console.log('Ignore non-critical playback API fail', error)
return new Promise(() => {})
}
Import with: import {Player} from 'playcraft'
.
Props of <VideoPlayer>
can also be used.
Need to give suitable configuration to drm
.
In this case, please use getEnterpriseDrmConfig
.
Example
import {getEnterpriseDrmConfig} from 'playcraft'
...
<Player
...
drm={getEnterpriseDrmConfig}
...
>
host
URL of playback server.
content
An object defines content to play, the shape is {contentType, contentId}
.
Possible content types are: videos
, lives
.
This object is extensibe if needed, like licenseId
is added to indetify license.
accessToken
Access token of current user, this is optional if access control is not needed.
This will be added to header Authorization
of playback API requests.
deviceId
Unique identifier of current device, needed for concurrent device count limit.
licenseKey
Bitmovin Player license, the player will start only if the domain name is localhost or in alowed list.
When starting a new project that using Bitmovin, contact core tech TPM or CPT team member to create a key in Bitmovin Dashboard.
config
Config for Base Player, use this only when you know what you are doing.
Now we support two base player: Shaka, Bitmovin.
Shaka Reference: https://shaka-player-demo.appspot.com/docs/api/tutorial-config.html
Bitmovin Reference: https://cdn.bitmovin.com/player/web/8/docs/interfaces/core_config.playerconfig.html
Shape:
{
basePlayer,
basePlayerConfig,
}
basePlayer content is shaka
, bitmovin
.
Default is:
{
basePlayer: 'shaka'
}
You could switch to Bitmovin by config
interface:
<Player
...
config={{basePlayer: 'bitmovin'}}
...
>
lang
This is used to switch the language displayed in the player, ex: tooltips of buttons, error messages.
Defaults to en
, available values are: en
, ja
, zh-TW
.
preload
Simillar to preload
of <video>
, controls preload behavior of player.
Options:
auto
(default): Automatically start playback session & fetch manifests.none
: Playback session will be started when.load()
is called, or when play button is tapped.
startTime
Start playback at specified time (seconds, factorial).
autoPlayNext
When playback time reached end_start_position
or end first time, show an UI with image of next episode prompt user to play next episode, and countdown for 10 seconds before playing automatically.
This takes effect of if the video have next episode.
Defaults to false
.
quality
Prefered quality to play, specify by height of resolution like 720
, 1080
, if specified height doesn't exist, a higher resolution will be used.
Since the player plays with adaptive bitrate, quality is actually defined max resultion can be adoptd.
On Safari or iOS Chrome which needs native HLS, this feature is disabled by default.
If you want to enable it, please use object form with qualitySelectionHack: true
.
User even could define custom quality text by object form:
<Player
quality={
default: 1080,
getQualityText: ({width, height}) => `${height}p`,
qualitySelectionHack: true
}
/>
Quality options can be customized with quality.getQualityOptions
, in this case, you should handle custom option in onVideoQualityChanged
handler:
<Player
quality={
default: 'auto',
getQualityOptions: qualities => [{
label: 'AUTO',
height: 'auto',
value: 'auto',
}].concat(qualities)
}
onVideoQualityChanged={({qualities, targetQuality}) => {
if (targetQuality.height === 'auto') {
// return nothing to use all available adaptation range
return
}
return [findQuality(quality, targetQuality)]
}}
/>
thumbnailSeeking
Option to enable / disable thumbnail seeking, if enabled but not available, thumbnails won't be shown.
Default is false
.
drm
�Strategy function to generate DRM configuration.
Currently, we provide two helper functions: getBVKDrmConfig
and getEnterpriseDrmConfig
.
The main difference between the two is authorization mechanism.
getEnterpriseDrmConfig
strategy places token to X-Custom-Data header in DRM flow.
getBVKDrmConfig
strategy sets token to authorization header instead of X-Custom-Data.
This function is for BlendVision Kaleido.
The appropriate strategy is based on your DRM service.
Default is getBVKDrmConfig
.
onError
Example:
;({
from, // The module where the error occurred. May be 'API', 'Player' and 'UI'.
error: {
code, // error code
message, // origin error message
},
content, // player content prop
}) => {}
onSourceLoaded
onReady
onPlay
onPlaying
onSeek
Example:
;(
seekTarget, // The target position (in seconds)
currentPosition // The current position (in seconds)
) => {}
onSeeked
Example:
// current position in seconds
currentPosition => {}
onPaused
onTimeChanged
Example:
// current position in seconds
currentPosition => {}
onVolumeChanged
Example:
;(
targetVolume, // The new selected volume.
sourceVolume // The volume before the event has been triggered.
) => {}
onMuted
onUnmuted
onStallStarted
onStallEnded
onReplay
onVideoQualityChanged
Triggered when user change quality in settings UI.
There are targetQuality
, qualities
in event data, you can return subset of qualities
to define desired range of quality adaptation.
This example shows how to fix at selected quality :
onVideoQualityChanged={({targetQuality, qualities}) =>
// assume target quality always exists
qualities.filter(({height}) => targetQuality.height === height)
}
onEnded
onEnterFullscreen
onExitFullscreen
onViewModeChange
onChangeVideo
Example:
;({
videoId, // This is the video id that the player wants to change.
}) => {}
onChangeToNextVideo
Example:
;({
videoId, // This is the video id that the player wants to change.
}) => {}
onChangeToPreviousVideo
Example:
;({
videoId, // This is the video id that the player wants to change.
}) => {}
Import from playcraft/plugins
.
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, and bundle size won't increace with unused features.
Since main bundle is not side-effect-free yet, plugins are in sub bundle playcraft/plugins
.
This plugin loads streams with server-side stitched ad from MediaTailor, and provides ad related functionalies.
Ad UI is not included in this plugin.
adParams
Set personalized ads for MediaTailor.
This props should be inserted with MediaTailorPlugin
contructor.
Default is empty JSON.
const adParams = {user: 'tim'}
const plugin = MediaTailorPlugin({adParams})
Features
- Load ad stitched streams from MediaTailor
- Load ad tracking event data & send tracking events(beacons)
- Snapback
- Provide playback time of original content
- Provide ad playback status
- Provide ad events
- Provide skip ad function
Example for React
To avoid re-initializing plugins on re-render, please wrap it with useMemo.
import {Player} from 'playcraft'
import {MediaTailorPlugin} from 'playcraft/plugins'
const MyPlayerView = () => {
const plugins = useMemo(() => [MediaTailorPlugin()], [])
return (
<MyContainer>
<Player plugins={plugins} />
</MyContainer>
)
}
Example for Cast receiver
This plugin can also integrate with Playcraft Cast receiver.
import {MediaTailorPlugin} from 'playcraft/plugins'
import {castService} from 'playcraft-google-cast'
castService.start({
plugins: [MediaTailorPlugin()],
})
This plugin enable the playcraft integration with ImaDai SDK for HTML5. You can use it as follow:
import {ImaDaiPlugin} from 'playcraft/plugins'
function ContainerComponent(props) {
const plugins = useMemo(() => [ImaDaiPlugin()], [])
return <PremiumPlusPlayer plugins={plugins} />
}
requestOptionOverrides
Accept an object to overrides the default StreamRequest:
const plugins = useMemo(
() => [
ImaDaiPlugin({
requestOptionsOverrides: {adTagParameters: {tfcd: 1}},
}),
],
[]
)
return <PremiumPlusPlayer plugins={plugins} />
Import with: import {mapLogEvents} from 'playcraft/modules
This sub bundle contains building blocks unplugged from enterprise player, for crafting a player from the super flexible minimal player or other players.
A observer operator-like funciton, take video element, generates playback log events to be sent to amplitude.
Cast receiver also handle playlog with this function.
Premium+ already integrated playlog in it, no need to use this function.
For premium player or other players, simply pass video element and pass additional events with logTarget.emit
:
const MyApp = () => {
const videoRef = useRef()
const logTarget = useRef()
useEffect(() => {
logTarget.current = mapLogEvents({
playerName: 'shaka',
version: process.env.VERSION,
video: videoRef.current,
})
}, [])
return (
<PremiumPlayer
videoRef={videoRef}
sendLog={(name, data) => logTarget.current.emit(name, data)}
/>
)
}
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.
A function handleIOSHeadphonesDisconnection
is provided to workaround this.
Example
import React, {useEffect} from 'react'
import {Player} from 'playcraft'
import {handleIOSHeadphonesDisconnection} from 'playcraft/modules'
const MyVideoComponent = () => {
useEffect(() => {
handleIOSHeadphonesDisconnection()
}, [])
return <Player />
}