Skip to content

Commit

Permalink
DE-6967: HLS Interstitial Player Component
Browse files Browse the repository at this point in the history
  • Loading branch information
fingerartur committed Apr 8, 2024
1 parent f0be44f commit 2a320b9
Show file tree
Hide file tree
Showing 12 changed files with 801 additions and 1 deletion.
6 changes: 5 additions & 1 deletion app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Asset, TestAssets } from './Asset'
import { BasicOverlayPage } from './BasicOverlayPage'
import { ComponentsOverviewPage } from './ComponentsOverviewPage'
import { CustomControlsPage } from './CustomControlsPage'
import { InterstitialPage } from './InterstitialPage'
import { YoutubeControlsPage } from './YoutubeControlsPage'

// load app styles
Expand All @@ -17,7 +18,7 @@ import '@castlabs/prestoplay/clpp.styles.css'
// load the theme
import '../../src/themes/pp-ui-base-theme.css'

type Page = 'basic' | 'custom' | 'components' | 'youtube'
type Page = 'basic' | 'custom' | 'components' | 'youtube' | 'interstitial'

function getQueryVariable(variable: string) {
const searchParams = new URLSearchParams(window.location.search)
Expand Down Expand Up @@ -60,6 +61,8 @@ export function App() {
return <ComponentsOverviewPage asset={asset} autoload={autoload}/>
} else if (pageId === 'youtube') {
return <YoutubeControlsPage asset={asset} autoload={autoload}/>
} else if (pageId === 'interstitial') {
return <InterstitialPage />
}
return <div>Unknown Page!</div>
}, [pageId, asset, autoload])
Expand Down Expand Up @@ -94,6 +97,7 @@ export function App() {
<button onClick={selectPage('custom')} className={`${pageId === 'custom' ? 'selected' : ''}`}>Custom Overlay</button>
<button onClick={selectPage('youtube')} className={`${pageId === 'youtube' ? 'selected' : ''}`}>Youtube Overlay</button>
<button onClick={selectPage('components')} className={`${pageId === 'components' ? 'selected' : ''}`}>Components</button>
<button onClick={selectPage('interstitial')} className={`${pageId === 'interstitial' ? 'selected' : ''}`}>HLS Interstitial</button>
</nav>

<div>
Expand Down
46 changes: 46 additions & 0 deletions app/src/InterstitialPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { clpp } from '@castlabs/prestoplay'
import React from 'react'

import { InterstitialPlayer } from '../../src'

/**
* A page featuring the HLS interstitial player.
*/
export const InterstitialPage = () => {
return (
<main className="in-page">
<div className="in-container">
<div className="in-video-container">
<InterstitialPlayer
asset={{
source: {
// url: 'http://localhost:3000/vod-fixed.m3u8',
url: 'http://localhost:3000/vod-preroll.m3u8',
type: clpp.Type.HLS,
},
}}
interstitialOptions={{
// Start resolving X-ASSET-LIST 15 seconds or less before
// the cue is scheduled
resolutionOffsetSec: 15,
}}
// onPlayerChanged={p => {
// // @ts-ignore
// window.player = p
// }}
// showInterstitialMarkers={false}
// seekStep={2}
// controlsVisibility='never'
// intermissionDuration={5}
// interstitialLabel={(i) => `Ad ${i.podOrder} of ${i.podCount}`}
// renderInterstitialLabel={(i) => null}
// renderIntermission={(seconds) => <div>{seconds}</div>}
// loop={false}
// onEnded={() => {}}
// onLoopEnded={() => {}}
/>
</div>
</div>
</main>
)
}
15 changes: 15 additions & 0 deletions app/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,18 @@ nav button:hover {
background-color: #b4b4b4;
}

.in-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.in-container {
width: 1000px;
}

.in-video-container {
width: 800px;
height: 580px;
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ export * from './components/VolumeBar'
export * from './components/PlayPauseIndicator'
export * from './components/VuMeter'

export * from './interstitial/InterstitialPlayer'

export * from './context/PrestoContext'
159 changes: 159 additions & 0 deletions src/interstitial/InterstitialPlayer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { clpp } from '@castlabs/prestoplay'
import '@castlabs/prestoplay/cl.mse'
import '@castlabs/prestoplay/cl.hls'
import React, { useEffect, useRef } from 'react'

import { ControlsVisibilityMode } from '../services/controls'

import { InterstitialOverlay } from './components/OverlayHlsi'
import { PlayerSurfaceHlsi } from './components/PlayerSurfaceHlsi'
import { PlayerHlsi } from './PlayerHlsi'
import { HlsInterstitial } from './types'

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
clpp.install(clpp.hls.HlsComponent)

export type InterstitialPlayerProps = {
/**
* HLS interstitial Asset
*/
asset: clpp.PlayerConfiguration
/**
* If the asset should be played back in a loop, one cycle of the loop
* consists of a few seconds of intermission and then the asset is played.
* Default: true.
*/
loop?: boolean
/**
* Intermission duration in seconds, defaults to 3.
*/
intermissionDuration?: number | null
/**
* Intermission element renderer. Default: Countdown
*/
renderIntermission?: (seconds: number) => (JSX.Element | null)
/**
* Callback called when playback ended (if loop is `false`)
*/
onEnded?: () => any
/**
* Callback called when one loop cycle ended (if loop is `true`)
*/
onLoopEnded?: () => any
/**
* Interstitial label text renderer. Default: `Interstitial ${podOrder} of ${podCount}`
*/
interstitialLabel?: (i: HlsInterstitial) => string
/**
* Interstitial label component renderer.
*/
renderInterstitialLabel?: (i: HlsInterstitial) => (JSX.Element | null)
/**
* Visibility mode of UI controls. Default: 'always-visible'
*/
controlsVisibility?: ControlsVisibilityMode
/**
* Seek step in seconds for seek buttons. A value of 0 will hide the buttons.
* Default: 10.
*/
seekStep?: number
/**
* If true interstitial markers should be shown on the timeline. Default: true.
*/
showInterstitialMarkers?: boolean
/**
* If true, a fullscreen button is displayed. Defaults to true.
*/
hasFullScreenButton?: boolean
/**
* If true, audio controls are displayed. Defaults to false.
*/
hasAudioControls?: boolean
/**
* If true, track controls are displayed. Defaults to false.
*/
hasTrackControls?: boolean
/**
* Callback called when the player of multi-controller changes.
*/
onPlayerChanged?: (p: clpp.Player) => any
/**
* Options for clpp.interstitial.Player
*/
interstitialOptions?: Omit<clpp.interstitial.Options, 'anchorEl'>
/**
* Custom class name for the player container.
*/
className?: string
/**
* Custom style for the player container.
*/
style?: React.CSSProperties
}

/**
* Interstitial player component.
*/
export const InterstitialPlayer = React.memo((props: InterstitialPlayerProps) => {
const playerRef = useRef(new PlayerHlsi())

useEffect(() => {
// Possibly it's something wrong with the AIP stream http://localhost:3000/vod-preroll.m3u8
// but unfortunately what happens is that we get state "Ended" and then the video
// continues playing for another cca 800ms. This would obviously cause a glitch
// in the UI so configure the player to ignore all ended states changes
playerRef.current.ignoreStateEnded = true
}, [])

const load = async () => {
try {
await playerRef.current.loadHlsi(props.asset)
} catch (e) {
console.error('Interstitial player Failed to load asset', e)
}
}

useEffect(() => {
if (props.onPlayerChanged) {
playerRef.current.onUIEvent('playerChanged', props.onPlayerChanged)
}

return () => {
if (props.onPlayerChanged) {
playerRef.current.offUIEvent('playerChanged', props.onPlayerChanged)
}
}
}, [])

let className = 'pp-ui-hlsi-player'
if (props.className) {
className += ` ${props.className}`
}

return (
<div className={className} style={props.style}>
<PlayerSurfaceHlsi
player={playerRef.current}
interstitialOptions={props.interstitialOptions}
>
<InterstitialOverlay
{...props}
onStartClick={async () => {
await load()
}}
onLoopEnded={async () => {
props.onLoopEnded?.()
await playerRef.current.reset()
await load()
}}
onIntermissionEnded={async () => {
await playerRef.current.unpause()
}}
/>
</PlayerSurfaceHlsi>
</div>
)
})

InterstitialPlayer.displayName = 'InterstitialPlayer'
Loading

0 comments on commit 2a320b9

Please sign in to comment.