Hooks

React hooks for accessing and managing player state. These hooks provide fine-grained access to player functionality and help prevent unnecessary re-renders in your components.

General Media Hooks

useMediaContext

Main hook that provides access to the complete media context. Use this when you need access to multiple player properties or when building custom components.

import React from 'react';
import { useMediaContext } from '@smart-tv/player';
function CustomPlayerInfo() {
const {
// Player state
currentTime,
duration,
volume,
muted,
paused,
loading,
error,
// Player actions
play,
pause,
seek,
setVolume,
setMuted,
// Player instance
player,
// Tracks
audioTracks,
videoTracks,
textTracks,
} = useMediaContext();
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div className="player-info">
<h3>Player Information</h3>
<p>Time: {Math.floor(currentTime)}s / {Math.floor(duration)}s</p>
<p>Volume: {Math.round(volume * 100)}% {muted && '(Muted)'}</p>
<p>Status: {paused ? 'Paused' : 'Playing'}</p>
<p>Audio Tracks: {audioTracks.length}</p>
<p>Video Tracks: {videoTracks.length}</p>
<p>Subtitles: {textTracks.length}</p>
<div className="controls">
<button onClick={() => paused ? play() : pause()}>
{paused ? 'Play' : 'Pause'}
</button>
<button onClick={() => setMuted(!muted)}>
{muted ? 'Unmute' : 'Mute'}
</button>
</div>
</div>
);
}

usePlayer

Provides access to the underlying Shaka Player instance for advanced operations.

import React, { useEffect } from 'react';
import { usePlayer } from '@smart-tv/player';
function AdvancedPlayerControls() {
const player = usePlayer();
useEffect(() => {
if (!player) return;
// Access Shaka Player APIs directly
const config = player.getConfiguration();
console.log('Player configuration:', config);
// Set up custom event listeners
const onBuffering = (event) => {
console.log('Buffering event:', event);
};
player.addEventListener('buffering', onBuffering);
return () => {
player.removeEventListener('buffering', onBuffering);
};
}, [player]);
const handleAdvancedSeek = (seconds) => {
if (player) {
const currentTime = player.getPlayheadTimeAsDate();
const newTime = new Date(currentTime.getTime() + seconds * 1000);
player.seekToLiveEdge();
}
};
return (
<div>
<button onClick={() => handleAdvancedSeek(30)}>
Skip 30s
</button>
<button onClick={() => player?.configure({
streaming: { bufferingGoal: 60 }
})}>
Increase Buffer
</button>
</div>
);
}

Optimized State Hooks

These hooks provide access to specific player state properties and help prevent unnecessary re-renders by only updating when their specific values change.

Playback State Hooks

usePaused

Returns whether the player is currently paused.

import { usePaused } from '@smart-tv/player';
function PlayPauseButton() {
const paused = usePaused();
return (
<button className="play-pause-btn">
{paused ? '▶️' : '⏸️'}
</button>
);
}

useCurrentTime & useDuration

Access current playback time and total duration.

import { useCurrentTime, useDuration } from '@smart-tv/player';
function TimeDisplay() {
const currentTime = useCurrentTime();
const duration = useDuration();
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
return (
<div className="time-display">
{formatTime(currentTime)} / {formatTime(duration)}
</div>
);
}

useTimeProgress

Returns playback progress as a percentage (0-100).

import { useTimeProgress } from '@smart-tv/player';
function ProgressIndicator() {
const progress = useTimeProgress();
return (
<div className="progress-container">
<div
className="progress-bar"
style={{ width: `${progress}%` }}
/>
<span className="progress-text">{Math.round(progress)}%</span>
</div>
);
}

useLoading

Returns whether the player is currently loading content.

import { useLoading } from '@smart-tv/player';
function LoadingSpinner() {
const loading = useLoading();
if (!loading) return null;
return (
<div className="loading-overlay">
<div className="spinner" />
<p>Loading...</p>
</div>
);
}

Volume Hooks

useVolume & useMuted

Access volume level and mute state.

import { useVolume, useMuted } from '@smart-tv/player';
function VolumeIndicator() {
const volume = useVolume();
const muted = useMuted();
const getVolumeIcon = () => {
if (muted || volume === 0) return '🔇';
if (volume < 0.3) return '🔈';
if (volume < 0.7) return '🔉';
return '🔊';
};
return (
<div className="volume-indicator">
<span className="volume-icon">{getVolumeIcon()}</span>
<span className="volume-level">
{muted ? 'Muted' : `${Math.round(volume * 100)}%`}
</span>
</div>
);
}

useVolumeState

Returns both volume and muted state in a single object.

import { useVolumeState } from '@smart-tv/player';
function VolumeControls() {
const { volume, muted } = useVolumeState();
return (
<div className="volume-controls">
<input
type="range"
min="0"
max="1"
step="0.1"
value={muted ? 0 : volume}
onChange={(e) => {
// Volume change logic handled by player context
}}
/>
<span>{muted ? 'Muted' : `${Math.round(volume * 100)}%`}</span>
</div>
);
}

Fullscreen & Picture-in-Picture Hooks

useFullscreen

Returns current fullscreen state.

import { useFullscreen } from '@smart-tv/player';
function FullscreenButton() {
const fullscreen = useFullscreen();
return (
<button className="fullscreen-btn">
{fullscreen ? '⛶' : '⛶'} {fullscreen ? 'Exit' : 'Enter'} Fullscreen
</button>
);
}

usePictureInPicture

Returns current picture-in-picture state.

import { usePictureInPicture } from '@smart-tv/player';
function PiPButton() {
const pictureInPicture = usePictureInPicture();
return (
<button className="pip-btn">
{pictureInPicture ? '📱➡️📺' : '📺➡️📱'}
{pictureInPicture ? 'Exit' : 'Enter'} PiP
</button>
);
}

Track Hooks

useAudioTracks

Returns available audio tracks and currently selected track.

import { useAudioTracks } from '@smart-tv/player';
function AudioTrackSelector() {
const audioTracks = useAudioTracks();
if (audioTracks.length <= 1) return null;
return (
<div className="audio-track-selector">
<label>Audio Language:</label>
<select
value={audioTracks.find(track => track.active)?.id || ''}
onChange={(e) => {
// Track selection logic handled by player context
console.log('Selected audio track:', e.target.value);
}}
>
{audioTracks.map(track => (
<option key={track.id} value={track.id}>
{track.label || track.language}
</option>
))}
</select>
</div>
);
}

useVideoTracks

Returns available video quality tracks and currently selected track.

import { useVideoTracks } from '@smart-tv/player';
function QualitySelector() {
const videoTracks = useVideoTracks();
const formatQuality = (track) => {
const quality = `${track.height}p`;
const bandwidth = track.bandwidth ?
` (${Math.round(track.bandwidth / 1000)}kbps)` : '';
return quality + bandwidth;
};
return (
<div className="quality-selector">
<label>Video Quality:</label>
<select
value={videoTracks.find(track => track.active)?.id || 'auto'}
onChange={(e) => {
console.log('Selected quality:', e.target.value);
}}
>
<option value="auto">Auto</option>
{videoTracks.map(track => (
<option key={track.id} value={track.id}>
{formatQuality(track)}
</option>
))}
</select>
</div>
);
}

useTextTracks

Returns available subtitle/caption tracks and currently selected track.

import { useTextTracks } from '@smart-tv/player';
function SubtitleSelector() {
const textTracks = useTextTracks();
return (
<div className="subtitle-selector">
<label>Subtitles:</label>
<select
value={textTracks.find(track => track.active)?.id || 'off'}
onChange={(e) => {
console.log('Selected subtitle:', e.target.value);
}}
>
<option value="off">Off</option>
{textTracks.map(track => (
<option key={track.id} value={track.id}>
{track.label || track.language} ({track.kind})
</option>
))}
</select>
</div>
);
}

Action Hooks

usePlayerActions

Provides action functions for controlling playback without causing re-renders.

import { usePlayerActions } from '@smart-tv/player';
function PlayerControlButtons() {
const {
play,
pause,
seek,
setVolume,
setMuted,
enterFullscreen,
exitFullscreen,
selectAudioTrack,
selectVideoTrack,
selectTextTrack,
} = usePlayerActions();
const handleSkip = (seconds) => {
// Seek relative to current time
seek({ relative: seconds });
};
const handleQualityChange = (trackId) => {
selectVideoTrack(trackId);
};
return (
<div className="player-controls">
<button onClick={play}>Play</button>
<button onClick={pause}>Pause</button>
<button onClick={() => handleSkip(-10)}>-10s</button>
<button onClick={() => handleSkip(10)}>+10s</button>
<button onClick={() => setVolume(0.5)}>50% Volume</button>
<button onClick={() => setMuted(true)}>Mute</button>
<button onClick={enterFullscreen}>Fullscreen</button>
</div>
);
}

usePlayerActionsOnly

Similar to usePlayerActions but guaranteed to never cause re-renders. Use this in components that only need to trigger actions.

import { usePlayerActionsOnly } from '@smart-tv/player';
function QuickActionButtons() {
const actions = usePlayerActionsOnly();
// This component will never re-render due to player state changes
return (
<div className="quick-actions">
<button onClick={() => actions.seek({ relative: -10 })}>
⏪ 10s
</button>
<button onClick={actions.togglePlayPause}>
⏯️
</button>
<button onClick={() => actions.seek({ relative: 10 })}>
10s ⏩
</button>
<button onClick={actions.toggleMute}>
🔇
</button>
<button onClick={actions.toggleFullscreen}>
</button>
</div>
);
}

Playlist Hooks

usePlaylist

Main playlist hook providing access to playlist state and actions.

import { usePlaylist } from '@smart-tv/player';
function PlaylistControls() {
const {
// State
items,
currentIndex,
currentItem,
hasNext,
hasPrevious,
isShuffled,
isLooped,
// Actions
playItem,
playNext,
playPrevious,
shuffle,
toggleLoop,
addItem,
removeItem,
} = usePlaylist();
return (
<div className="playlist-controls">
<div className="current-item">
<h3>{currentItem?.title}</h3>
<p>Item {currentIndex + 1} of {items.length}</p>
</div>
<div className="playlist-actions">
<button
onClick={playPrevious}
disabled={!hasPrevious}
>
⏮️ Previous
</button>
<button
onClick={playNext}
disabled={!hasNext}
>
⏭️ Next
</button>
<button
onClick={shuffle}
className={isShuffled ? 'active' : ''}
>
🔀 Shuffle
</button>
<button
onClick={toggleLoop}
className={isLooped ? 'active' : ''}
>
🔁 Loop
</button>
</div>
</div>
);
}

usePlaylistState & usePlaylistActions

Separated hooks for playlist state and actions to optimize performance.

import { usePlaylistState, usePlaylistActions } from '@smart-tv/player';
// Component that only displays playlist state (won't re-render on action calls)
function PlaylistDisplay() {
const { items, currentIndex, currentItem } = usePlaylistState();
return (
<div className="playlist-display">
<h2>Now Playing</h2>
<div className="current-track">
<img src={currentItem?.poster} alt={currentItem?.title} />
<div>
<h3>{currentItem?.title}</h3>
<p>{currentItem?.description}</p>
</div>
</div>
<p>Track {currentIndex + 1} of {items.length}</p>
</div>
);
}
// Component that only handles actions (minimal re-renders)
function PlaylistActionButtons() {
const { playNext, playPrevious, shuffle, toggleLoop } = usePlaylistActions();
return (
<div className="playlist-actions">
<button onClick={playPrevious}>⏮️</button>
<button onClick={playNext}>⏭️</button>
<button onClick={shuffle}>🔀</button>
<button onClick={toggleLoop}>🔁</button>
</div>
);
}

Custom Hook Examples

Examples of building custom hooks using the base player hooks.

Custom Progress Hook

import { useCurrentTime, useDuration } from '@smart-tv/player';
// Custom hook for formatted progress information
function usePlayerProgress() {
const currentTime = useCurrentTime();
const duration = useDuration();
const formatTime = (seconds) => {
const hrs = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hrs > 0) {
return `${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
return {
currentTime,
duration,
progress: duration > 0 ? (currentTime / duration) * 100 : 0,
remainingTime: duration - currentTime,
formattedCurrentTime: formatTime(currentTime),
formattedDuration: formatTime(duration),
formattedRemainingTime: formatTime(duration - currentTime),
};
}
// Usage
function ProgressDisplay() {
const {
progress,
formattedCurrentTime,
formattedDuration,
formattedRemainingTime
} = usePlayerProgress();
return (
<div className="progress-display">
<div className="time-info">
<span>{formattedCurrentTime}</span>
<span>-{formattedRemainingTime}</span>
<span>{formattedDuration}</span>
</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
</div>
);
}

Custom Keyboard Controls Hook

import { useEffect } from 'react';
import { usePlayerActions, usePaused, useVolume } from '@smart-tv/player';
function useKeyboardControls(enabled = true) {
const actions = usePlayerActions();
const paused = usePaused();
const volume = useVolume();
useEffect(() => {
if (!enabled) return;
const handleKeyPress = (event) => {
// Prevent default browser behavior
switch (event.code) {
case 'Space':
event.preventDefault();
paused ? actions.play() : actions.pause();
break;
case 'ArrowLeft':
event.preventDefault();
actions.seek({ relative: -10 });
break;
case 'ArrowRight':
event.preventDefault();
actions.seek({ relative: 10 });
break;
case 'ArrowUp':
event.preventDefault();
actions.setVolume(Math.min(1, volume + 0.1));
break;
case 'ArrowDown':
event.preventDefault();
actions.setVolume(Math.max(0, volume - 0.1));
break;
case 'KeyM':
event.preventDefault();
actions.toggleMute();
break;
case 'KeyF':
event.preventDefault();
actions.toggleFullscreen();
break;
case 'Escape':
event.preventDefault();
actions.exitFullscreen();
break;
}
};
document.addEventListener('keydown', handleKeyPress);
return () => document.removeEventListener('keydown', handleKeyPress);
}, [enabled, actions, paused, volume]);
}
// Usage
function PlayerWithKeyboardControls() {
useKeyboardControls(true);
return (
<div className="player-with-keyboard">
<p>Keyboard shortcuts enabled:</p>
<ul>
<li>Space: Play/Pause</li>
<li>←/→: Seek ±10s</li>
<li>↑/↓: Volume ±10%</li>
<li>M: Toggle Mute</li>
<li>F: Toggle Fullscreen</li>
</ul>
</div>
);
}

Best Practices

✅ Do

  • • Use specific hooks (usePaused) instead of general ones (useMediaContext)
  • • Use usePlayerActionsOnly for action-only components
  • • Create custom hooks for complex logic
  • • Separate state and actions when possible
  • • Use React.memo for components that render frequently

❌ Avoid

  • • Using useMediaContext when you only need one property
  • • Calling hooks conditionally
  • • Creating effects that depend on rapidly changing values
  • • Accessing player instance directly unless necessary
  • • Overusing useCallback/useMemo with hook values