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