npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smart-tv/player

v1.3.4

Published

A comprehensive Smart TV video player built with Shaka Player and spatial navigation support

Readme

@smart-tv/player

A comprehensive, production-ready video player built specifically for Smart TV applications. Powered by Shaka Player with built-in navigation, this player delivers a professional streaming experience optimized for remote control navigation.

Features

  • 🎥 Shaka Player Integration - Industry-leading adaptive streaming engine
  • 🎯 Navigation - Built-in remote control and keyboard navigation
  • 📱 Responsive Design - Adapts to TV, tablet, and mobile screens
  • 🔐 DRM Support - Widevine, PlayReady, FairPlay for protected content
  • 🌐 Multi-format Support - DASH, HLS, and progressive download
  • 🎭 Multiple Layouts - Netflix, YouTube, TV Remote, Mobile, and custom layouts
  • 📺 Picture-in-Picture - Modern multi-tasking viewing experience
  • 🎨 Fully Customizable - Component-based architecture with Tailwind CSS
  • 🔊 Audio/Video Tracks - Multiple audio languages and quality levels
  • 📝 Subtitles/Captions - Full subtitle and closed caption support
  • Optimized Performance - Selective re-rendering and efficient state management
  • 📋 Playlist Support - Queue, autoplay, and playlist management
  • 🔄 Playback Controls - Speed control, seek, volume, and fullscreen
  • 🎬 Auto-play Countdown - Netflix-style next episode countdown

Installation

Install the package and its peer dependencies:

# npm
npm install @smart-tv/player

# pnpm
pnpm add @smart-tv/player

# yarn
yarn add @smart-tv/player

Import Styles

Import the CSS file in your app:

import "@smart-tv/player/styles.css";

Quick Start

Basic Player

import { VideoPlayer, PlayerController, MediaProvider } from "@smart-tv/player";
import "@smart-tv/player/styles.css";

function App() {
  return (
    <MediaProvider>
      <div className="relative h-screen w-full bg-black">
        <VideoPlayer
          src="https://example.com/video.m3u8"
          poster="https://example.com/poster.jpg"
          autoPlay={false}
        />
        <PlayerController layout="netflix" />
      </div>
    </MediaProvider>
  );
}

With Smart TV Navigation

import { AppProvider } from "@smart-tv/ui";
import { VideoPlayer, PlayerController, MediaProvider } from "@smart-tv/player";
import "@smart-tv/ui/styles.css";
import "@smart-tv/player/styles.css";

function App() {
  return (
    <AppProvider init={{ debug: false, visualDebug: false }}>
      <MediaProvider>
        <div className="relative h-screen w-full bg-black">
          <VideoPlayer
            src="https://example.com/video.m3u8"
            onReady={() => console.log("Player ready")}
            onError={(error) => console.error("Error:", error)}
          />
          <PlayerController
            layout="tv-remote"
            autoHide={true}
            autoHideDelay={3000}
          />
        </div>
      </MediaProvider>
    </AppProvider>
  );
}

Core Concepts

MediaProvider

The MediaProvider is the main context provider that manages player state. It must wrap all player components.

import { MediaProvider } from "@smart-tv/player";

<MediaProvider>{/* Player components */}</MediaProvider>;

VideoPlayer

The core video player component powered by Shaka Player:

import { VideoPlayer } from "@smart-tv/player";

<VideoPlayer
  src="https://example.com/video.m3u8"
  poster="https://example.com/poster.jpg"
  autoPlay={false}
  loop={false}
  muted={false}
  volume={1.0}
  playbackRate={1.0}
  onReady={() => console.log("Ready")}
  onPlay={() => console.log("Playing")}
  onPause={() => console.log("Paused")}
  onError={(error) => console.error(error)}
/>;

PlayerController

A pre-configured control layout with multiple built-in styles:

import { PlayerController } from "@smart-tv/player";

<PlayerController
  layout="netflix" // 'netflix' | 'youtube' | 'tv-remote' | 'mobile' | 'minimal'
  autoHide={true}
  autoHideDelay={3000}
  showPlaylist={false}
/>;

Available Layouts

Netflix Layout

<PlayerController layout="netflix" />
  • Full-featured controls
  • Large play button
  • Progress bar with thumbnails
  • Track selection panel

YouTube Layout

<PlayerController layout="youtube" />
  • Bottom control bar
  • Integrated settings menu
  • Theater and fullscreen modes

TV Remote Layout

<PlayerController layout="tv-remote" />
  • Optimized for remote control navigation
  • Large, focusable buttons
  • Simplified interface

Mobile Layout

<PlayerController layout="mobile" />
  • Touch-optimized controls
  • Minimal UI
  • Gesture support

Minimal Layout

<PlayerController layout="minimal" />
  • Basic play/pause and seek
  • Clean, minimal interface

Components

Control Components

PlayButton

import { PlayButton } from "@smart-tv/player";

<PlayButton
  size="lg" // 'sm' | 'md' | 'lg'
  variant="default" // 'default' | 'ghost' | 'outline'
  focusKey="play-btn"
  showIcon={true}
/>;

SeekBar

import { SeekBar } from "@smart-tv/player";

<SeekBar
  showPreview={true}
  showThumbnails={false}
  stepTime={10} // Seek step in seconds
  focusKey="seek-bar"
/>;

VolumeControl

import { VolumeControl } from "@smart-tv/player";

<VolumeControl
  orientation="horizontal" // 'horizontal' | 'vertical'
  showMuteButton={true}
  step={0.1}
  focusKey="volume"
/>;

Fullscreen

import { Fullscreen } from "@smart-tv/player";

<Fullscreen focusKey="fullscreen-btn" />;

PictureInPicture

import { PictureInPicture } from "@smart-tv/player";

<PictureInPicture focusKey="pip-btn" />;

Track Components

TrackSelector

Select audio, video quality, or subtitles:

import { TrackSelector } from "@smart-tv/player";

<TrackSelector
  type="audio" // 'audio' | 'video' | 'text'
  title="Select Audio Track"
  onClose={() => setShowTracks(false)}
/>;

AudioTrack

import { AudioTrack } from "@smart-tv/player";

<AudioTrack focusKey="audio-tracks" />;

VideoTrack (Quality Selector)

import { VideoTrack } from "@smart-tv/player";

<VideoTrack focusKey="video-quality" />;

TextTrack (Subtitles)

import { TextTrack } from "@smart-tv/player";

<TextTrack focusKey="subtitles" />;

SpeedSelector

import { SpeedSelector } from "@smart-tv/player";

<SpeedSelector focusKey="speed" />;

SettingsPanel

Complete settings overlay with all track options:

import { SettingsPanel } from "@smart-tv/player";

<SettingsPanel
  isOpen={showSettings}
  onClose={() => setShowSettings(false)}
  focusKey="settings"
/>;

Playlist Components

PlaylistProvider

Context provider for playlist management:

import { PlaylistProvider } from "@smart-tv/player";

<PlaylistProvider
  initialItems={playlistItems}
  config={{
    autoPlay: true,
    autoPlayDelay: 5,
    showThumbnails: true,
    loop: false,
  }}
  callbacks={{
    onItemPlay: (item) => console.log("Playing:", item.title),
    onItemEnd: (item) => console.log("Ended:", item.title),
  }}
>
  {/* Player components */}
</PlaylistProvider>;

Playlist

Full playlist UI component:

import { Playlist } from "@smart-tv/player";

<Playlist
  isOpen={showPlaylist}
  onClose={() => setShowPlaylist(false)}
  focusKey="playlist"
/>;

PlaylistRail

Horizontal scrolling playlist rail:

import { PlaylistRail } from "@smart-tv/player";

<PlaylistRail
  rail={{
    id: "queue",
    title: "Up Next",
    type: "queue",
    items: queueItems,
  }}
  focusKey="playlist-rail"
/>;

AutoPlayCountdown

Netflix-style countdown for next episode:

import { AutoPlayCountdown } from "@smart-tv/player";

<AutoPlayCountdown
  nextItem={nextEpisode}
  countdown={5}
  onCancel={() => console.log("Cancelled")}
  onPlayNext={() => console.log("Playing next")}
/>;

PlaylistButton

Button to toggle playlist visibility:

import { PlaylistButton } from "@smart-tv/player";

<PlaylistButton
  onClick={() => setShowPlaylist(!showPlaylist)}
  focusKey="playlist-btn"
/>;

Hooks

useMediaContext

Access the complete media context:

import { useMediaContext } from "@smart-tv/player";

function CustomControl() {
  const { state, player, actions } = useMediaContext();

  return (
    <button onClick={actions.togglePlay}>
      {state.paused ? "Play" : "Pause"}
    </button>
  );
}

Optimized Hooks

Use these hooks to prevent unnecessary re-renders:

usePlayerState

import { usePlayerState } from "@smart-tv/player";

const { currentTime, duration, paused, volume } = usePlayerState();

usePaused

import { usePaused } from "@smart-tv/player";

const paused = usePaused();

useCurrentTime

import { useCurrentTime } from "@smart-tv/player";

const currentTime = useCurrentTime();

useDuration

import { useDuration } from "@smart-tv/player";

const duration = useDuration();

useVolume

import { useVolume } from "@smart-tv/player";

const { volume, muted } = useVolume();

usePlayerActions

import { usePlayerActions } from "@smart-tv/player";

const { play, pause, seek, setVolume } = usePlayerActions();

useAudioTracks

import { useAudioTracks } from "@smart-tv/player";

const audioTracks = useAudioTracks();

useVideoTracks

import { useVideoTracks } from "@smart-tv/player";

const videoTracks = useVideoTracks();

useTextTracks

import { useTextTracks } from "@smart-tv/player";

const textTracks = useTextTracks();

useFullscreen

import { useFullscreen } from "@smart-tv/player";

const { isFullscreen, enterFullscreen, exitFullscreen } = useFullscreen();

usePictureInPicture

import { usePictureInPicture } from "@smart-tv/player";

const { isPip, enterPip, exitPip } = usePictureInPicture();

Playlist Hooks

usePlaylist

import { usePlaylist } from "@smart-tv/player";

const { state, actions, helpers } = usePlaylist();

usePlaylistState

import { usePlaylistState } from "@smart-tv/player";

const { currentItemId, rails, isVisible } = usePlaylistState();

usePlaylistActions

import { usePlaylistActions } from "@smart-tv/player";

const { playItem, playNext, playPrevious, togglePlaylist } =
  usePlaylistActions();

Advanced Usage

DRM Protected Content

import { VideoPlayer, MediaProvider } from "@smart-tv/player";

const drmConfig = {
  src: "https://example.com/protected-video.mpd",
  drm: {
    servers: {
      "com.widevine.alpha": "https://license.example.com/widevine",
      "com.microsoft.playready": "https://license.example.com/playready",
    },
    advanced: {
      "com.widevine.alpha": {
        videoRobustness: "SW_SECURE_CRYPTO",
        audioRobustness: "SW_SECURE_CRYPTO",
      },
    },
  },
};

function ProtectedVideo() {
  return (
    <MediaProvider>
      <VideoPlayer
        src={[drmConfig]}
        onError={(error) => console.error("DRM Error:", error)}
      />
    </MediaProvider>
  );
}

Custom Controls Layout

import {
  MediaProvider,
  VideoPlayer,
  PlayButton,
  SeekBar,
  VolumeControl,
  Fullscreen,
  SettingsPanel,
} from "@smart-tv/player";
import { useState } from "react";

function CustomPlayer() {
  const [showSettings, setShowSettings] = useState(false);

  return (
    <MediaProvider>
      <div className="relative h-screen w-full bg-black">
        <VideoPlayer src="https://example.com/video.m3u8" />

        {/* Custom control layout */}
        <div className="absolute inset-0 flex items-end bg-gradient-to-t from-black/80 to-transparent p-8">
          <div className="w-full space-y-4">
            <SeekBar className="w-full" showPreview />

            <div className="flex items-center justify-between">
              <div className="flex items-center gap-4">
                <PlayButton size="lg" />
                <VolumeControl orientation="horizontal" />
              </div>

              <div className="flex gap-4">
                <button
                  className="rounded bg-white/20 px-4 py-2 hover:bg-white/30"
                  onClick={() => setShowSettings(true)}
                >
                  Settings
                </button>
                <Fullscreen />
              </div>
            </div>
          </div>
        </div>

        <SettingsPanel
          isOpen={showSettings}
          onClose={() => setShowSettings(false)}
        />
      </div>
    </MediaProvider>
  );
}

Playlist with Auto-play

import {
  MediaProvider,
  PlaylistProvider,
  VideoPlayer,
  PlayerController,
  Playlist,
  AutoPlayCountdown,
} from "@smart-tv/player";

const episodes = [
  {
    id: "1",
    title: "Episode 1",
    url: "https://example.com/ep1.m3u8",
    thumbnail: "https://example.com/ep1.jpg",
    duration: 2400,
  },
  {
    id: "2",
    title: "Episode 2",
    url: "https://example.com/ep2.m3u8",
    thumbnail: "https://example.com/ep2.jpg",
    duration: 2520,
  },
];

function SeriesPlayer() {
  return (
    <MediaProvider>
      <PlaylistProvider
        initialItems={episodes}
        config={{
          autoPlay: true,
          autoPlayDelay: 5,
          autoPlayCountdown: true,
        }}
        callbacks={{
          onItemEnd: (item) => console.log("Finished:", item.title),
          onAutoPlayStart: (nextItem, countdown) =>
            console.log(`Playing ${nextItem.title} in ${countdown}s`),
        }}
      >
        <div className="relative h-screen w-full bg-black">
          <VideoPlayer />
          <PlayerController layout="netflix" showPlaylist />
          <AutoPlayCountdown />
        </div>
      </PlaylistProvider>
    </MediaProvider>
  );
}

Multiple Audio/Subtitle Tracks

import { VideoPlayer, MediaProvider, useMediaContext } from "@smart-tv/player";
import { useEffect } from "react";

function MultiTrackPlayer() {
  const { player, actions } = useMediaContext();

  useEffect(() => {
    if (player) {
      // Get available tracks
      const audioTracks = player.getAudioTracks();
      const textTracks = player.getTextTracks();

      console.log("Audio tracks:", audioTracks);
      console.log("Subtitle tracks:", textTracks);

      // Select specific track
      // actions.selectAudioTrack('eng-audio-track-id')
      // actions.selectTextTrack('eng-subtitle-track-id')
    }
  }, [player, actions]);

  return (
    <MediaProvider>
      <VideoPlayer src="https://example.com/multi-track-video.m3u8" />
    </MediaProvider>
  );
}

Custom Player Layouts

You can create completely custom player layouts by defining a PlayerControllerLayout configuration. This gives you full control over button placement, styling, and behavior.

Creating a Custom Layout

import {
  MediaProvider,
  VideoPlayer,
  PlayerController,
  PlayerControllerLayout,
  PlayerButtonConfig,
  PlayerButtonAction,
} from "@smart-tv/player";

function CustomLayoutPlayer() {
  // Define your custom button configurations
  const customButtons: PlayerButtonConfig[] = [
    // Title and subtitle at top
    {
      action: "title",
      align: "top",
      position: "bottom",
      showTitle: true,
      showSubtitle: true,
    },
    // Play/pause button
    {
      action: "playpause",
      align: "left",
      position: "bottom",
      size: "lg",
      className: "player-rounded-full player-fill-white",
      selectedClass: "player-bg-primary player-bg-opacity-100",
      order: 1,
    },
    // Settings button
    {
      action: "settings",
      align: "left",
      position: "bottom",
      className: "player-rounded-full player-fill-white p-2",
      selectedClass: "player-bg-primary player-bg-opacity-100",
      order: 2,
    },
    // Custom button with popup
    {
      action: "custom",
      align: "left",
      position: "bottom",
      label: "Info",
      icon: <InfoIcon />,
      className: "player-rounded-full player-fill-white p-2",
      selectedClass: "player-bg-primary player-bg-opacity-100",
      popup: true,
      content: (
        <div className="player-text-white player-p-4">
          <h2 className="player-text-xl player-font-bold">Video Information</h2>
          <p className="player-mt-2">Custom content here...</p>
        </div>
      ),
      order: 3,
    },
    // Progress bar at bottom
    {
      action: "progressbar",
      align: "bottom",
      position: "bottom",
    },
  ];

  // Create the layout configuration
  const customLayout: PlayerControllerLayout = {
    name: "My Custom Layout",
    description: "Custom player layout with specific buttons",
    showOnHover: false,
    autoHide: true,
    autoHideDelay: 5000,
    background: "bg-black bg-opacity-60",
    padding: "player-p-8",
    gap: "gap-4",
    buttons: customButtons,
  };

  return (
    <MediaProvider>
      <div className="relative h-screen w-full bg-black">
        <VideoPlayer src="https://example.com/video.m3u8" />
        <PlayerController
          layout={customLayout}
          title="My Video Title"
          subtitle="Season 1 - Episode 1"
          onButtonPress={(action, config) => {
            console.log("Button pressed:", action, config);
          }}
        />
      </div>
    </MediaProvider>
  );
}

Button Configuration Options

Each button in the layout can be configured with these properties:

interface PlayerButtonConfig {
  action: PlayerButtonAction; // Button action type (required)
  position: "top" | "center" | "bottom"; // Vertical position (required)
  align: "left" | "center" | "right"; // Horizontal alignment (required)

  // Appearance
  label?: string; // Button label
  icon?: ReactNode; // Custom icon
  size?: "xs" | "sm" | "md" | "lg" | "xl"; // Button size
  className?: string; // CSS classes for default state
  selectedClass?: string; // CSS classes when focused/selected
  style?: CSSProperties; // Inline styles

  // Behavior
  visible?: boolean; // Show/hide button
  disabled?: boolean; // Enable/disable button
  order?: number; // Display order (lower = first)
  focusKey?: string; // Custom focus key for navigation

  // Callbacks
  onPress?: (event?) => void; // Click/press handler
  onRelease?: () => void; // Release handler
  onFocus?: () => void; // Focus handler
  onBlur?: () => void; // Blur handler

  // Popup/Overlay
  popup?: boolean; // Show as popup overlay
  content?: ReactNode; // Popup content
  tooltip?: string; // Tooltip text
}

Available Button Actions

type PlayerButtonAction =
  // Playback
  | "play"
  | "pause"
  | "playpause"
  | "stop"

  // Volume
  | "mute"
  | "unmute"
  | "mutetoggle"
  | "volume"
  | "volumebar"

  // Fullscreen & PiP
  | "fullscreen"
  | "exitfullscreen"
  | "fullscreentoggle"
  | "pip"
  | "exitpip"
  | "piptoggle"

  // Seeking
  | "seek"
  | "seekbar"
  | "progressbar"
  | "rewind"
  | "forward"

  // Navigation
  | "previous"
  | "next"

  // Tracks & Settings
  | "playlist"
  | "playlisttoggle"
  | "settings"
  | "quality"
  | "subtitles"
  | "audio"
  | "playbackrate"

  // Information
  | "title"
  | "time"
  | "duration"
  | "live"
  | "info"

  // Custom
  | "like"
  | "share"
  | "download"
  | "custom";

Advanced Custom Layout Example

Here's a more complex example with conditional buttons and dynamic content:

import { useMemo, useState } from "react";
import {
  PlayerController,
  PlayerControllerLayout,
  PlayerButtonConfig,
} from "@smart-tv/player";

function AdvancedCustomPlayer() {
  const [isLive, setIsLive] = useState(false);
  const [isSeries, setIsSeries] = useState(true);
  const [inWatchlist, setInWatchlist] = useState(false);

  const customLayout: PlayerControllerLayout = useMemo(
    () => ({
      name: "Smart TV Layout",
      description: "Optimized for TV remote navigation",
      showOnHover: false,
      autoHide: true,
      autoHideDelay: 6000,
      background: "bg-gradient-to-t from-black via-black/80 to-transparent",
      padding: "player-p-6",
      gap: "gap-3",
      buttons: [
        // Title info at top
        {
          action: "title",
          align: "top",
          position: "bottom",
          showTitle: true,
          showSubtitle: true,
        },
        // Main play button
        {
          action: "playpause",
          align: "left",
          position: "bottom",
          size: "xl",
          className: "player-rounded-full player-bg-white player-text-black",
          selectedClass: "player-ring-4 player-ring-blue-500",
          order: 1,
        },
        // Playlist button (only for series)
        ...(isSeries
          ? [
              {
                action: "custom" as const,
                align: "left" as const,
                position: "bottom" as const,
                label: "Episodes",
                icon: <PlaylistIcon />,
                onPress: () => console.log("Show playlist"),
                className:
                  "player-rounded-full player-bg-white/20 player-text-white",
                selectedClass:
                  "player-bg-blue-500 player-ring-2 player-ring-white",
                order: 2,
              },
            ]
          : []),
        // Settings
        {
          action: "settings",
          align: "left",
          position: "bottom",
          className: "player-rounded-full player-bg-white/20 player-text-white",
          selectedClass: "player-bg-blue-500",
          order: 3,
        },
        // Watchlist button
        {
          action: "custom",
          align: "left",
          position: "bottom",
          label: inWatchlist ? "In List" : "Add to List",
          icon: inWatchlist ? <CheckIcon /> : <PlusIcon />,
          onPress: () => setInWatchlist(!inWatchlist),
          className: "player-rounded-full player-bg-white/20 player-text-white",
          selectedClass: "player-bg-green-500",
          order: 4,
        },
        // Info button with popup
        {
          action: "custom",
          align: "left",
          position: "bottom",
          label: "Details",
          icon: <InfoIcon />,
          popup: true,
          content: (
            <div className="player-max-w-2xl player-p-6 player-bg-gray-900 player-rounded-lg">
              <h2 className="player-text-2xl player-font-bold player-text-white">
                Video Details
              </h2>
              <p className="player-mt-4 player-text-gray-300">
                Full video description and metadata...
              </p>
            </div>
          ),
          className: "player-rounded-full player-bg-white/20 player-text-white",
          selectedClass: "player-bg-blue-500",
          order: 5,
        },
        // Progress bar (hide for live content)
        ...(!isLive
          ? [
              {
                action: "progressbar" as const,
                align: "bottom" as const,
                position: "bottom" as const,
                progressClassName: "player-h-1 player-bg-red-600",
                timerStyle: "leftRight" as const,
              },
            ]
          : []),
        // Fullscreen button (right side)
        {
          action: "fullscreentoggle",
          align: "right",
          position: "bottom",
          className: "player-rounded-full player-bg-white/20 player-text-white",
          selectedClass: "player-bg-blue-500",
          order: 10,
        },
      ],
    }),
    [isSeries, isLive, inWatchlist]
  );

  return (
    <PlayerController
      layout={customLayout}
      title="The Great Adventure"
      subtitle="Season 2 - Episode 5: The Journey Begins"
      onButtonPress={(action, config) => {
        console.log("Button action:", action);
        // Handle button presses
      }}
    />
  );
}

Position & Alignment Guide

Buttons are positioned using a combination of position (vertical) and align (horizontal):

Top Section:
┌─────────────────────────────────────────┐
│  left    center    right                │  position: 'top'
├─────────────────────────────────────────┤

Center Section:
│  left    center    right                │  position: 'center'

Bottom Section:
├─────────────────────────────────────────┤
│  left    center    right                │  position: 'bottom'
└─────────────────────────────────────────┘

Special alignments for progress/title:

  • align: 'top' - Above main content (for titles)
  • align: 'bottom' - Below main content (for progress bars)

Using Pre-defined Layouts as Base

You can also extend pre-defined layouts:

import { netflixLayout, PlayerControllerLayout } from "@smart-tv/player";

const myLayout: PlayerControllerLayout = {
  ...netflixLayout,
  buttons: [
    ...netflixLayout.buttons,
    {
      action: "custom",
      align: "left",
      position: "bottom",
      label: "My Custom Button",
      icon: <CustomIcon />,
      onPress: () => console.log("Custom action"),
    },
  ],
};

TypeScript Support

Full type safety for custom layouts:

import {
  PlayerControllerLayout,
  PlayerButtonConfig,
  PlayerButtonAction,
  PlayerButtonPosition,
  PlayerButtonAlign,
} from "@smart-tv/player";

const buttons: PlayerButtonConfig[] = [
  {
    action: "playpause" as PlayerButtonAction,
    position: "bottom" as PlayerButtonPosition,
    align: "left" as PlayerButtonAlign,
    size: "lg",
  },
];

const layout: PlayerControllerLayout = {
  name: "My Layout",
  buttons,
  autoHide: true,
  autoHideDelay: 5000,
};

Complete Example

Here's a comprehensive example showing multiple features:

import { AppProvider } from "@smart-tv/ui";
import {
  MediaProvider,
  PlaylistProvider,
  VideoPlayer,
  PlayerController,
  Playlist,
  AutoPlayCountdown,
  usePlaylistActions,
} from "@smart-tv/player";
import "@smart-tv/ui/styles.css";
import "@smart-tv/player/styles.css";

const episodes = [
  {
    id: "1",
    title: "S01E01 - Pilot",
    description: "The beginning of an epic journey",
    url: "https://example.com/s01e01.m3u8",
    thumbnail: "https://example.com/s01e01.jpg",
    duration: 2400,
    subtitles: [
      {
        url: "https://example.com/s01e01-en.vtt",
        language: "en",
        label: "English",
        isDefault: true,
      },
    ],
  },
  // More episodes...
];

function TVApp() {
  return (
    <AppProvider init={{ debug: false, visualDebug: false }}>
      <MediaProvider>
        <PlaylistProvider
          initialItems={episodes}
          config={{
            autoPlay: true,
            autoPlayDelay: 5,
            autoPlayCountdown: true,
            showThumbnails: true,
            loop: false,
          }}
          callbacks={{
            onItemPlay: (item) => console.log("Now playing:", item.title),
            onItemEnd: (item) => console.log("Finished:", item.title),
            onAutoPlayStart: (next, countdown) =>
              console.log(`Next in ${countdown}s: ${next.title}`),
          }}
        >
          <div className="relative h-screen w-full bg-black">
            <VideoPlayer
              autoPlay
              onReady={() => console.log("Player ready")}
              onError={(error) => console.error("Error:", error)}
            />

            <PlayerController
              layout="netflix"
              autoHide
              autoHideDelay={3000}
              showPlaylist
            />

            <AutoPlayCountdown />
          </div>
        </PlaylistProvider>
      </MediaProvider>
    </AppProvider>
  );
}

export default TVApp;

TypeScript Support

Full TypeScript support with comprehensive type definitions:

import {
  PlayerState,
  PlayerSource,
  DrmConfig,
  AudioTrack,
  VideoTrack,
  TextTrack,
  PlaylistItem,
  PlaylistConfig,
  MediaPlayerInstance,
} from "@smart-tv/player";

// Type-safe player configuration
const source: PlayerSource = {
  src: "https://example.com/video.m3u8",
  type: "application/x-mpegURL",
  drm: {
    servers: {
      "com.widevine.alpha": "https://license.example.com",
    },
  },
};

// Type-safe playlist
const playlist: PlaylistItem[] = [
  {
    id: "1",
    title: "Episode 1",
    url: "https://example.com/ep1.m3u8",
    thumbnail: "https://example.com/thumb.jpg",
    duration: 2400,
  },
];

Utilities

formatTime

Format seconds to HH:MM:SS or MM:SS:

import { formatTime } from "@smart-tv/player";

formatTime(3665); // "01:01:05"
formatTime(125); // "02:05"

cn (className utility)

Merge Tailwind classes:

import { cn } from "@smart-tv/player";

<div className={cn("base-class", focused && "focused-class")} />;

debounce / throttle

Performance utilities:

import { debounce, throttle } from "@smart-tv/player";

const debouncedFn = debounce(() => console.log("Debounced"), 300);
const throttledFn = throttle(() => console.log("Throttled"), 300);

Documentation

For comprehensive documentation, interactive examples, and best practices, visit:

📚 https://smart-tv-docs.vercel.app/components/player

📦 NPM Package

Styling

The package uses Tailwind CSS. Ensure Tailwind is configured:

// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@smart-tv/player/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@smart-tv/ui/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      // Your custom theme
    },
  },
  plugins: [],
};

Browser Support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • Smart TV browsers (Tizen 4.0+, webOS 4.0+, Android TV 9+)
  • Requires ES2018+ support

Performance Tips

  1. Use optimized hooks - Import specific hooks like usePaused() instead of usePlayerState() to prevent unnecessary re-renders
  2. Lazy load components - Load heavy components like SettingsPanel only when needed
  3. Optimize playlists - Use virtualization for large playlists
  4. Preload thumbnails - Preload thumbnail images for better UX
  5. Enable adaptive streaming - Let Shaka Player handle quality based on bandwidth

Troubleshooting

Player not loading

  • Ensure MediaProvider wraps all player components
  • Check that Shaka Player is installed: npm list shaka-player
  • Verify video source URL is accessible
  • Check browser console for errors

Navigation not working

  • Ensure AppProvider from @smart-tv/ui wraps your app
  • Check that focusKey props are unique
  • Enable debug mode: <AppProvider init={{ debug: true }}>

DRM not working

  • Verify DRM license server URLs are correct
  • Check browser DRM support: Chrome (Widevine), Safari (FairPlay)
  • Review DRM configuration in browser console

Development

Building the package

# Install dependencies
pnpm install

# Build
pnpm --filter=@smart-tv/player build

# Development mode
pnpm --filter=@smart-tv/player dev

Contributing

Contributions are welcome! Please follow the monorepo conventions and add tests for new features.

See CONTRIBUTING.md for more details.

Related Packages

License

BSD 3-Clause License - see LICENSE for details.

Support

Acknowledgments

  • Built with Shaka Player by Google
  • Inspired by Netflix, YouTube, and modern streaming platforms
  • Designed for the Smart TV development community

Made with ❤️ for Smart TV developers