Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DE-7019: Enrich InterstitalPlayer API #41

Merged
merged 8 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ function setQueryParam(key: string, value: string) {
export function App() {
// We track the configuration here to make sure we can dynamically change it
const [assetId, setAssetId] = useState<number>(Number(getQueryVariable('asset') || 0))
const [pageId, setPageId] = useState<Page>(getQueryVariable('page') ?? 'basic')
const [pageId, setPageId] = useState<Page>(getQueryVariable('page') ?? 'interstitial')
const [asset, setAsset] = useState<Asset|undefined>(TestAssets[assetId])
const [autoload, setAutoload] = useState<boolean>(false)
const [navVisible, setNavVisible] = useState<boolean>(false)
Expand Down
55 changes: 54 additions & 1 deletion app/src/InterstitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ export const InterstitialPage = () => {
<InterstitialPlayer
asset={{
source: {
// url: 'http://localhost:3000/vod-fixed.m3u8',
// url: 'http://localhost:3000/vod-fixed.m3u8',
url: 'http://localhost:3000/vod-preroll.m3u8',
// url: 'https://9457688946fc45ac9a3b526e93b06bf7.us-west-2.alpha.mediatailor.aws.a2z.com/v1/master/5d22c610440c419b9290f9233dc99fe61adb77ab/mt-dev-vod/index.m3u8?aws.insertionMode=GUIDED',
type: clpp.Type.HLS,
},
}}
Expand All @@ -34,10 +35,62 @@ export const InterstitialPage = () => {
// 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
patchIgnoreStateEnded={true}
hasTopControlsBar={false}
interstitialOptions={{
// Start resolving X-ASSET-LIST 15 seconds or less before
// the cue is scheduled
resolutionOffsetSec: 15,
interstitialAssetConverter: (asset: clpp.interstitial.PlayerItem) => {
asset.config.htmlcue = {
enableResizeObserver: false,
}
return asset
},
}}
renderTopCompanion={(isFullScreen) => {
if (!isFullScreen) {return null}
return <div className="in-logo-container"><img className="in-logo" src="./logo.png"/></div>
}}
interstitialControls={{
pause: true,
seekButtons: false,
time: false,
fullScreen: true,
audio: false,
}}
onIntermissionEnded={() => {
console.info('EEEEvent: intermission-ended playback or primary or preroll started')
}}
onHlsiPlayerReady={hp => {
hp.on('cues-changed', (event) => {
const cues = hp.getCues()
console.info('EEEEvent: cues-changed', event.detail, 'cues via api call', cues)
})

hp.on('interstitial-started', (event) => {
console.info('EEEEvent: interstitial-started', event.detail)
})

hp.on('interstitial-item-started', (event) => {
// There are multiple items in one interstitial
console.info('EEEEvent: interstitial-item-started', event.detail)
})

hp.on('interstitial-ended', (event) => {
console.info('EEEEvent: interstitial-ended', event.detail)
})

hp.on('primary-started', (event) => {
console.info('EEEEvent: primary-started', event.detail)
})

hp.on('playback-started', (event) => {
console.info('EEEEvent: playback-started (primary or preroll)', event.detail)
})

hp.on('primary-ended', (event) => {
console.info('EEEEvent: primary-ended', event.detail)
})
}}
// onPlayerChanged={p => {
// // @ts-ignore
Expand Down
15 changes: 14 additions & 1 deletion app/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,18 @@ nav button:hover {

.in-video-container {
width: 800px;
height: 580px;
height: 400px;
}

.in-logo-container {
width: 100%;
display: flex;
justify-content: end;
padding: 10px;
position: relative;
top: 10px;
}

.in-logo {
width: 200px;
}
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"@babel/preset-env": "^7.22.4",
"@babel/preset-react": "^7.22.3",
"@babel/preset-typescript": "^7.21.5",
"@castlabs/prestoplay": "^6.11.1-beta.1",
"@castlabs/prestoplay": "^6.11.1-beta.2",
"@finga/eslint-config": "^1.2.1",
"@rollup/plugin-commonjs": "^23.0.2",
"@rollup/plugin-image": "^3.0.2",
Expand Down
2 changes: 1 addition & 1 deletion src/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export class Player {
/**
* Indicate that the config was loaded
*/
private _configLoaded = false
protected _configLoaded = false
/**
* UI control visibility manager
*/
Expand Down
43 changes: 34 additions & 9 deletions src/components/BaseThemeOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BufferingIndicator } from './BufferingIndicator'
import { CurrentTime } from './CurrentTime'
import { Duration } from './Duration'
import { ForSize } from './ForSize'
import { FullscreenButton } from './FullscreenButton'
import { FullscreenButton, useIsPlayerFullScreen } from './FullscreenButton'
import { HorizontalBar } from './HorizontalBar'
import { Label } from './Label'
import {
Expand Down Expand Up @@ -81,14 +81,26 @@ export interface BaseThemeOverlayProps extends BaseComponentProps {
* If true, track controls are displayed. Defaults to true.
*/
hasTrackControls?: boolean
/**
* If true, the play/pause button is displayed. Defaults to true.
*/
hasPauseButton?: boolean
/**
* If true, the top controls bar is displayed. Defaults to true.
*/
hasTopControlsBar?: boolean
/**
* If true, the time is displayed. Defaults to true.
*/
hasTime?: boolean
/**
* Render a custom bottom companion component.
*/
renderBottomCompanion?: () => (JSX.Element | null)
renderBottomCompanion?: (isFullScreen: boolean) => (JSX.Element | null)
/**
* Render a custom top companion component.
*/
renderTopCompanion?: (isFullScreen: boolean) => (JSX.Element | null)
/**
* If true, seek bar cues are shown. Default: true.
*/
Expand All @@ -109,6 +121,10 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => {
const hasFullScreenButton = props.hasFullScreenButton ?? true
const hasTopControlsBar = props.hasTopControlsBar ?? true
const showSeekBarCues = props.showSeekBarCues ?? true
const hasPauseButton = props.hasPauseButton ?? true
const hasTime = props.hasTime ?? true

const isFullScreen = useIsPlayerFullScreen()

const renderOptionsMenu = () => {
if (selectionOptions.length === 0) {return}
Expand Down Expand Up @@ -139,13 +155,20 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => {
return <StartButton onClick={typeof props.startButton === 'object' ? props.startButton.onClick : undefined}/>
}

const topCompanion = props.renderTopCompanion?.(isFullScreen)

return (
<div data-testid="pp-ui-basic-theme" className={'pp-ui pp-ui-overlay pp-ui-basic-theme'} style={props.style}>
<PlayerControls mode={props.controlsVisibility}>
<VerticalBar className={'pp-ui-spacer'}>

{/* Top bar */}
{renderTopBar()}
{topCompanion ? (
<div className="pp-ui pp-ui-row pp-ui-top-bar">
{topCompanion}
</div>
): null}

<Spacer/>

Expand All @@ -154,12 +177,12 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => {
<Thumbnail moveRelativeToParent={true}/>
</HorizontalBar>

{props.renderBottomCompanion?.()}
{props.renderBottomCompanion?.(isFullScreen)}

{/* Bottom bar */}
<HorizontalBar className="pp-ui-flex-space-between">
<div className="pp-ui-row pp-ui-margin-horizontal-sm">
<PlayPauseButton resetRate={true}/>
{hasPauseButton ? <PlayPauseButton resetRate={true}/> : null}
<ForSize size="small">
<SeekButton seconds={props.seekBackward ?? -10}/>
<SeekButton seconds={props.seekForward ?? 10}/>
Expand All @@ -176,11 +199,13 @@ export const BaseThemeOverlay = (props: BaseThemeOverlayProps) => {
/>}

<div className="pp-ui-row pp-ui-margin-horizontal-sm">
<ForSize size="small">
<CurrentTime />
<Label label={'/'}/>
<Duration />
</ForSize>
{hasTime ? (
<ForSize size="small">
<CurrentTime />
<Label label={'/'}/>
<Duration />
</ForSize>
): null}

{hasAudioControls ? <MuteButton/>: null}

Expand Down
11 changes: 10 additions & 1 deletion src/components/FullscreenButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const getVideoChild = (element: HTMLElement) => {
* This hook will return whether the element of its descendant video
* element is currently in fullscreen mode.
*/
const useIsFullscreen = (playerSurface: HTMLElement | null) => {
export const useIsFullscreen = (playerSurface: HTMLElement | null) => {
const [is, setIs] = useState(fullscreen.isInFullscreen())

const listener = () => {
Expand All @@ -100,6 +100,15 @@ const useIsFullscreen = (playerSurface: HTMLElement | null) => {
return is
}

/**
* This hooks returns true if the player is in fullscreen mode, false otherwise.
*/
export const useIsPlayerFullScreen = () => {
const { playerSurface } = useContext(PrestoContext)
const isFullscreen = useIsFullscreen(playerSurface)
return isFullscreen
}

/**
* Fullscreen button.
* A button that brings the player into fullscreen mode.
Expand Down
25 changes: 23 additions & 2 deletions src/interstitial/InterstitialPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ControlsVisibilityMode } from '../services/controls'
import { InterstitialOverlay } from './components/OverlayHlsi'
import { PlayerSurfaceHlsi } from './components/PlayerSurfaceHlsi'
import { PlayerHlsi } from './PlayerHlsi'
import { HlsInterstitial } from './types'
import { HlsInterstitial, InterstitialControls } from './types'

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand Down Expand Up @@ -41,6 +41,10 @@ export type InterstitialPlayerProps = {
* Callback called when one loop cycle ended (if loop is `true`)
*/
onLoopEnded?: () => any
/**
* Callback called when intermission ended
*/
onIntermissionEnded?: () => any
/**
* Interstitial label text renderer. Default: `Interstitial ${podOrder} of ${podCount}`
*/
Expand Down Expand Up @@ -74,6 +78,10 @@ export type InterstitialPlayerProps = {
* If true, track controls are displayed. Defaults to false.
*/
hasTrackControls?: boolean
/**
* If true, the top controls bar is displayed. Defaults to true.
*/
hasTopControlsBar?: boolean
/**
* Callback called when the player of multi-controller changes.
*/
Expand All @@ -94,6 +102,18 @@ export type InterstitialPlayerProps = {
* If true, the player will ignore all state changes to state "ended".
*/
patchIgnoreStateEnded?: boolean
/**
* Render a custom top companion component.
*/
renderTopCompanion?: (isFullScreen: boolean) => (JSX.Element | null)
/**
* Player controls to shown during interstitial playback.
*/
interstitialControls?: InterstitialControls
/**
* Callback to get the instance of the HLS interstitial player
*/
onHlsiPlayerReady?: (player: clpp.interstitial.Player) => void
}

/**
Expand All @@ -103,7 +123,7 @@ export type InterstitialPlayerProps = {
* intermission in between.
*/
export const InterstitialPlayer = React.memo((props: InterstitialPlayerProps) => {
const playerRef = useRef(new PlayerHlsi())
const playerRef = useRef(new PlayerHlsi(props.onHlsiPlayerReady))

useEffect(() => {
if (props.patchIgnoreStateEnded) {
Expand Down Expand Up @@ -161,6 +181,7 @@ export const InterstitialPlayer = React.memo((props: InterstitialPlayerProps) =>
await load()
}}
onIntermissionEnded={async () => {
props.onIntermissionEnded?.()
await playerRef.current.unpause()
}}
/>
Expand Down
9 changes: 9 additions & 0 deletions src/interstitial/PlayerHlsi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export class PlayerHlsi extends Player {
private _isPlayingInterstitial = false
private _disposers: Disposer[] = []

constructor(
private _onReady?: (p: clpp.interstitial.Player) => void,
) {
super()
}

/**
* Initialize the HLS Interstitial player
*/
Expand All @@ -53,6 +59,7 @@ export class PlayerHlsi extends Player {

this._options = options
this._ip = new clpp.interstitial.Player(options)
this._onReady?.(this._ip)

this.on('interstitial-item-started', (event) => {
this.emitUIEvent('hlsInterstitial', {
Expand Down Expand Up @@ -98,6 +105,8 @@ export class PlayerHlsi extends Player {
async loadHlsi(config?: clpp.PlayerConfiguration) {
if (!this._ip || !config) {return}
await this._ip.loadPaused(config)
// To enabled play/pause button
this._configLoaded = true
}

/**
Expand Down
Loading
Loading