@blendvision/player
v2.27.2
Published
Enjoy our latest update where we have fixed some bugs and improved our framework to provide you more stable playbacking experience.
Readme
Playcraft
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.
Developer Guide
We use husky v5 to handle git hooks.
Need to setup the husky environment in first time.
yarn install:huskyNow, commitlint and eslint checking work well in every commit.
Getting Started
Install this package from git repository:
yarn add @blendvision/playerAnd install Shaka player:
yarn add shaka-playerImport, 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>
)
}Legacy Browser Support
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 :
- ResizeObserver
Array.prototype.at
Low Latency Live Mode
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})
}}
/>
)
}API Reference
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
loadPlayer
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.
Plain JavaScript interface
While we can use the player object directly, this package also provides functions the works with all base players.
import {subscribePlaybackState, load, playOrPause, seek} from 'playcraft/core'<Video>
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
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}
>Source
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.drm:
;[
{
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',
},
},
},
]Extranal text track / subtitles
To add external text track, specify another source object with type: 'text/vtt' or type: 'text/srt':
;[
{
type: 'dash',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/dash.mpd',
},
{
type: 'text/vtt',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/text_ex.vtt',
},
{
type: 'text/srt',
src: 'https://storage.googleapis.com/shaka-demo-assets/bbb-dark-truths/text_ex.srt',
}Props for Player Options
shaka
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.
Props for Playback
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.
If not specified, volume will be saved to local storage on change if available, and restore next time.
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.
<PremiumPlayer>
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
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}},
],
},Props
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 isAutoplayBlocked of pause event.
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.
loop='disabled'
In addition to true | false for HTML video loop, you may also specify 'disabled' to disable built-in loop menu item in settings UI.
chapters
Show chapters related UI: SeekBar and SideBar.
const chapters = [
{
startTime: 0,
displayName: 'OP',
},
{
startTime: 30,
displayName: 'Content',
},
{
startTime: 190,
displayName: 'Ending',
},
]
<PremiumPlayer chapters={chapters} />intl.locale
Language of settings UI & error messages.
intl.messages
Custom translations.
title, channelTitle
Video title text to be displayed at top.
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()
}
}
>
)
}modulesConfig
The common configuration for all modules.
Each module config is distinguished by the key prefix name:
const modulesConfig = {
'moduleA.name': 'moduleA',
'moduleB.name': 'moduleB',
}Currently, only support analytics module:
const modulesConfig = {
'analytics.token': 'token',
'analytics.session_id': 'sessionId',
}
<PremiumPlayer
modulesConfig={modulesConfig}
/>normalizeTabSwitch
This property controls the default behavior of our PremiumPlayer when switching tabs on various versions of Safari and across different devices
If
normalizeTabSwitchdoesn't be set, the video will pause when the tab is not visible and resume when the tab becomes visible again. This is the default behavior.If
normalizeTabSwitchis set tofalse, the video will not pause when the tab is not visible.
Here's an example of how to use the normalizeTabSwitch property:
<PremiumPlayer
modulesConfig={{
normalizeTabSwitch: false,
}}
/>keyboardControls
The default behavior of keyboardControls is below:
- Play/Pause: Spacebar or 'K'
- Rewind: Left Arrow
- Forward: Right Arrow
- Volume Up: Up Arrow
- Volume Down: Down Arrow
- Mute/Unmute: 'M'
- Volume scroll: mouse scroll / gesture upward or downward
Focus Mode: Set mode to "focus-enable" to make the player respond to keyboard events only when focused.
Example of focus mode:
const customKeyboardControls = {
mode: "focus-enable"
};
<PremiumPlayer keyboardControls={customKeyboardControls} />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()}>slots
An object that contains the slots for a component. You can use it to define additional custom slots for a component's interior elements.
For example, the code snippet below shows how to replace the volume control with a custom component:
const MyVolumeControl = () => {
return <div>My Custom Volume Control</div>
}
<PremiumPlayer slots={{VolumeControl: MyCustomVolumeControl}} />Here are some possible slots that can be used in the <PremiumPlayer> component:
PlayButton: Used to display the play button, make sure to enable autoplay if you want to hide the play button, or add a custom play button.RewindButton/ForwardButton: Used to display the rewind/forward buttons.Seekbar: Used to display the seekbar of the video.VolumeControl: Used to control the volume of the video.DisplayTime: Used to display the current time of the video.Settings: Used to display the settings UI.FullscreenButton: Used to display the fullscreen button.
You can also define custom slots and slotProps to further customize the UI components of the player.
slotProps
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.
You may also use slotProps even if you don't use slots to override the default UI components.
For example, the code snippet below shows how to make the volume control horizontal:
<PremiumPlayer slotProps={{volumeControl: {slider: 'horizontal'}}}>uiElements (deprecated)
Deprecated, use slots and slotProps instead.
Inner UI Components
List of component props available for slots, slotProps override.
Seekbar
In case you need custom seekbar, replace it by slots.Seekbar like a whole red bar for classic live UI:
const Seekbar = () => (<div style={{height: '0.3em', background: 'red'}} />)
<PremiumPlayer slots={{
Seekbar,
}} />DisplayTime
VolumeControl
slider:'vertical'(default) | 'horizontal'
Settings
slots.root: Root element of Settings UI, you may replace withClassicSettingsContainerfor classic settings UI look.closeBy:'swipeDown'(default) | 'button'buttonPosition:'top-right' | 'bottom-right'
<PremiumPlayer
slotProps={{
closeBy: 'button',
slots: {root: ClassicSettingsContainer},
}}
/>UI Component Composition
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>Add UI elements to the right or left of the title, position defaults to 'left'.
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 position="left">
<div style={{width: '2rem'}} />
</TitleBarExtension>
</PremiumPlayer>UI Addons
Clips & Tags
This example shows how to enable clips & tags.
- Specify
metadata.clips - Use
SeekbarClipsTrackasSeekbarTrackslot - Other components are included in
Addons
import {SeekbarClipsTrack, usePlayerUiAddon} from 'playcraft/react'
const MyComponent = () => {
const {addonProps, addonPlayerProps} = usePlayerUiAddon({title, metadata, playlist})
// In case you need to sync displayed tags on the seekbar, use `onChangeTagFilter`
useEffect(() => {
if (0) {
addonProps.onChangeTagFilter({green: true, tagName1: true})
}
}, [])
return (
<PremiumPlayer
metadata={{
clips: [
{
name: 'Clip 1',
startTime: 0,
duration: 30,
tags: [{color: 'green'}],
}
]
}}
slotProps={{seekbar: {slots: {SeekbarTrack: SeekbarClipsTrack}}}}
{...addonPlayerProps}
>
<Addons title={eventTitle} {...metadata} activePanel={activePanel} onOpen={setActivePanel} {...addonProps} />
</PremiumPlayer>
)
}To replace the panel with a custom component, use Addons slot:
<Addons slots={{ChapterList: () => 'My Component'}}><PremiumPlusPlayer>
This component is moved to another package and left here for backward compatibility, no futher updates/fixes will be made.
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.
Props for enterprise features
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
}Props for playback API
Updating host, accessToken, deviceId, headers or params doesn't restart existing playback session, sub subseqent requests will be send with new prop values.
To restart a session, please update contentKey instead.
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.
contentKey
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.
<PremiumPlusPlayer
contentKey="linear/1"
contentType="live"
contentId="1"
preloadList={[
params={{playback_type: 'linear'}}
preloadList{[
{contentType: 'lives', contentId: '1', contentKey: 'whatever/1', params: {playback_type: 'whatever'},
{contentType: 'lives', contentId: '2', contentKey: 'linear/2', params: {playback_type: 'linear'},
]}
]}
/>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
/startand/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(() => {})
}Plugins
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.
⚠️ When using plugins with React UI, make sure the plugins are stored with a reference and are not initialized on re-render(see React example below).
Since main bundle is not side-effect-free yet, plugins are in sub bundle playcraft/plugins.
MediaTailorPlugin
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
⚠️ Warning: this prop is experimental.
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()],
})ImaDaiPlugin
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} />Modules
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.
mapLogEvents
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)}
/>
)
}enableTsSupport
Enable TS playback support, this needs mux.js to be installed, the effect applies glaboally, and only needs to be called once.
This is required for TS playback in non-Safari browsers.
yarn add mux.jsimport {enableTsSupport} from 'playcraft/modules'
enableTsSupport()Google Cast Sender Integration
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.
React integration
For React integration, enable Cast by adding initSender and <CastSender> to your app.
<PremiumPlayer> have a Cast button, which will display automatically when Google Cast sender is initialized and there's some available receiver ready for connection.
import {CastSessionControl} from 'playcraft/react'
useEffect(() => {
initSender({appId: "XXXXXX"});
},[])
<CastSessionControl intl={{locale: 'ja'}} />To add customData for your own custom logic, use cast.customData prop of <PremiumPlayer>:
<PremiumPlayer cast={{
contentId: '1',
customData: {
host: 'https://example.com',
accessToken: '1234567890',
customHeader: {},
customQuery: {},
},
onChange: handleCastChange,
}}>You may also handle Cast connection state change with cast.onChange.
Disabling Airplay Support
Disabling Airplay Support
Airplay support can be controlled using the x-webkit-wirelessvideoplaybackdisabled attribute. This attribute is specific to WebKit-based browsers and can be used to disable wireless video playback, effectively disabling Airplay.
In JavaScript, you can disable Airplay on a media element by setting the x-webkit-wirelessvideoplaybackdisabled attribute to 'true'. Here's how you can do it:
// Get a reference to your media element
const media = document.querySelector('your-media-selector');
// Set the attribute to 'true' to disable Airplay
media.setAttribute('x-webkit-wirelessvideoplaybackdisabled', 'true');In a React, you can pass this attribute through a prop to your video player component. Here's an example using a hypothetical PremiumPlayer component:
// Define your video attributes
const videoAttributes = {
'x-webkit-wirelessvideoplaybackdisabled': 'true',
};
// Pass the attributes to your player component
<PremiumPlayer {...videoAttributes} />Workarounds
Pause when Unplugging Headphones in iOS
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 />
}