@scrippsproduct/shadowstream
v0.0.34
Published
`React + TypeScript + Vite`
Keywords
Readme
ShadowStream Player
React + TypeScript + Vite
Get Started
To install the local video player and get setup for dev:
npm install
Install nvm:https://github.com/nvm-sh/nvm if you don't already have it or are not running newest node version
nvm install v20.17.0
nvm use v20.17.0
and then run the install
Running a local video player
Storybook for development and testing
npm run storybook
This will start Storybook at http://localhost:6006 where you can interact with and test the HLS player component in various configurations.
build for development
npm run build-stage
build package and publish
npm run build:lib
NEW* watch script
npm run watch:full
Linting
The project uses ESLint for code quality checks. Here are the available commands:
Check for linting issues
npm run lint
Automatically fix linting issues where possible
npm run lint:fix
Generate a lint report
npm run lint:report
This will create an eslint-report.json file with detailed linting information.
VS Code Integration
For the best development experience, install the ESLint extension for VS Code to get real-time linting feedback as you code.
Building for deployment
Update the version
npm version patch
npm run build
This will build files to the dist/ folder: dist/assets/index-v<version>.js dist/assets/index-v<version>.css
Then these files will need to be uploaded to s3 for deployment (this can be done with CI/CD pipelines on gitlab)
Additional info about this project
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- @vitejs/plugin-react uses Babel for Fast Refresh
- @vitejs/plugin-react-swc uses SWC for Fast Refresh
Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level
parserOptionsproperty like this:
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})- Replace
tseslint.configs.recommendedtotseslint.configs.recommendedTypeCheckedortseslint.configs.strictTypeChecked - Optionally add
...tseslint.configs.stylisticTypeChecked - Install eslint-plugin-react and update the config:
// eslint.config.js
import react from 'eslint-plugin-react'
export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})ShadowStream External API Documentation
Overview
The ShadowStream video player library provides an external API that allows your website to control playlist navigation and receive events from embedded video players. This is useful when you need to synchronize the video player with your site's UI or implement custom playlist controls.
Setup
When you embed a ShadowStream player, it automatically exposes a global API that can be accessed from your website's JavaScript. Each player instance is identified by its verizonId (if provided) or an auto-generated unique ID.
Accessing the API
The global API is available at window.shadowstream[instanceId], where instanceId is either:
- The
verizonIdyou provided when creating the player - An auto-generated ID in the format
shadowstream_[timestamp]_[random]
Finding Available Instances
// Get all available player instances
console.log(Object.keys(window.shadowstream || {}));
// Check if a specific instance exists
if (window.shadowstream && window.shadowstream['your-player-id']) {
// Player is available
}Playlist Control Methods
Jump to Video by ID
Jump to a specific video in the playlist using its unique ID:
const player = window.shadowstream['your-player-id'];
const success = await player.playlist.jumpToVideoById('video-123');
console.log('Jump successful:', success);Jump to Video by Index
Jump to a specific video using its position in the playlist (0-based):
const player = window.shadowstream['your-player-id'];
const success = await player.playlist.jumpToVideoByIndex(2); // Jump to 3rd video
console.log('Jump successful:', success);Navigate to Next/Previous Video
const player = window.shadowstream['your-player-id'];
// Go to next video
const nextSuccess = await player.playlist.nextVideo();
// Go to previous video
const prevSuccess = await player.playlist.previousVideo();Get Playlist Information
const player = window.shadowstream['your-player-id'];
// Get the complete playlist
const playlist = await player.playlist.getCurrentPlaylist();
console.log('Playlist:', playlist);
// Get the currently playing video
const currentVideo = await player.playlist.getCurrentVideo();
console.log('Current video:', currentVideo);
// Get the current video index
const currentIndex = await player.playlist.getCurrentIndex();
console.log('Current index:', currentIndex);Player Control Methods
In addition to playlist navigation, the external API provides comprehensive player control methods for managing playback, volume, seeking, and more.
Playback Controls
Control the player's playback state:
const player = window.shadowstream['your-player-id'];
// Start playback
player.play();
// Pause playback
player.pause();
// Toggle between play and pause
player.togglePlayPause();Seeking and Volume
Navigate within the video and control audio:
const player = window.shadowstream['your-player-id'];
// Seek to a specific time (in seconds)
player.seek(30); // Seek to 30 seconds
// Set volume (0.0 to 1.0)
player.setVolume(0.8); // Set volume to 80%
// Toggle mute state
player.toggleMute();
// Toggle fullscreen mode
player.toggleFullscreen();Video Navigation
Navigate between videos in the playlist:
const player = window.shadowstream['your-player-id'];
// Go to next video (returns boolean)
const hasNext = player.nextVideo();
// Skip the current video
player.skipVideo();Getting Player State
Access current player properties and state:
const player = window.shadowstream['your-player-id'];
// Get current playback time (in seconds)
const currentTime = player.getCurrentTime();
// Get total video duration (in seconds)
const duration = player.getDuration();
// Get current playlist
const playlist = player.getPlaylist();
// Get current video item
const currentItem = player.getCurrentItem();
// Access state properties
console.log('Is playing:', player.isPlaying);
console.log('Current time:', player.currentTime);
console.log('Duration:', player.duration);
console.log('Volume:', player.volume);
console.log('Is muted:', player.isMuted);Complete Player Control Example
Here's an example that demonstrates various player control operations:
const player = window.shadowstream['your-player-id'];
// Create custom player controls
function createCustomControls() {
// Play/Pause button
document.getElementById('play-pause-btn').onclick = () => {
player.togglePlayPause();
updateButtonText();
};
// Seek buttons
document.getElementById('seek-backward').onclick = () => {
const currentTime = player.getCurrentTime();
player.seek(Math.max(0, currentTime - 10)); // Seek back 10 seconds
};
document.getElementById('seek-forward').onclick = () => {
const currentTime = player.getCurrentTime();
const duration = player.getDuration();
player.seek(Math.min(duration, currentTime + 10)); // Seek forward 10 seconds
};
// Volume controls
document.getElementById('volume-up').onclick = () => {
const newVolume = Math.min(1, player.volume + 0.1);
player.setVolume(newVolume);
};
document.getElementById('volume-down').onclick = () => {
const newVolume = Math.max(0, player.volume - 0.1);
player.setVolume(newVolume);
};
// Mute toggle
document.getElementById('mute-btn').onclick = () => {
player.toggleMute();
};
// Fullscreen toggle
document.getElementById('fullscreen-btn').onclick = () => {
player.toggleFullscreen();
};
}
function updateButtonText() {
const button = document.getElementById('play-pause-btn');
button.textContent = player.isPlaying ? 'Pause' : 'Play';
}
// Initialize controls when player is ready
if (player) {
createCustomControls();
}Centralized API Access (Alternative Pattern)
You can also access player controls through the centralized API:
// Get a specific player instance
const player = window.shadowstream.getPlayer('your-player-id');
if (player) {
player.play();
player.setVolume(0.5);
}
// Control all players at once
window.shadowstream.pauseAll(); // Pause all players
window.shadowstream.playAll(); // Play all players
// Get all player instances
const allPlayers = window.shadowstream.getAllPlayers();
Object.keys(allPlayers).forEach(instanceId => {
console.log(`Player ${instanceId} is playing:`, allPlayers[instanceId].isPlaying);
});Event Listening
You can listen for events from the video player to stay synchronized with playback state:
Available Events
videoChanged: Fired when the current video changesplaylistUpdated: Fired when the playlist is updated with new videosvideoEnded: Fired when a video endsvideoStarted: Fired when a video starts playing
Adding Event Listeners
const player = window.shadowstream['your-player-id'];
// Listen for video changes
player.addEventListener((event) => {
if (event.type === 'videoChanged') {
console.log('Video changed:', event.data);
// event.data contains: { currentVideo, currentIndex }
} else if (event.type === 'playlistUpdated') {
console.log('Playlist updated:', event.data);
// event.data contains: { playlist }
}
});Removing Event Listeners
const player = window.shadowstream['your-player-id'];
// Define your listener function
const myListener = (event) => {
console.log('Event received:', event);
};
// Add the listener
player.addEventListener(myListener);
// Remove the listener later
player.removeEventListener(myListener);Complete Example
Here's a complete example that demonstrates how to integrate ShadowStream with your website:
<!DOCTYPE html>
<html>
<head>
<title>ShadowStream Integration Example</title>
</head>
<body>
<!-- Your website content -->
<div id="my-playlist">
<h3>Custom Playlist Controls</h3>
<button id="prev-btn">Previous</button>
<button id="next-btn">Next</button>
<div id="current-video-info"></div>
<ul id="playlist-items"></ul>
</div>
<!-- ShadowStream player -->
<div id="shadowstream-player">
<!-- Player will be embedded here -->
</div>
<script>
// Wait for the player to be ready
function waitForPlayer(instanceId, callback) {
if (window.shadowstream && window.shadowstream[instanceId]) {
callback(window.shadowstream[instanceId]);
} else {
setTimeout(() => waitForPlayer(instanceId, callback), 100);
}
}
// Initialize when player is ready
waitForPlayer('your-player-id', (player) => {
console.log('ShadowStream player ready!');
// Set up event listener
player.addEventListener((event) => {
if (event.type === 'videoChanged') {
updateCurrentVideoInfo(event.data.currentVideo);
} else if (event.type === 'playlistUpdated') {
updatePlaylistDisplay(event.data.playlist);
}
});
// Set up navigation buttons
document.getElementById('prev-btn').onclick = async () => {
const success = await player.playlist.previousVideo();
if (!success) {
alert('No previous video available');
}
};
document.getElementById('next-btn').onclick = async () => {
const success = await player.playlist.nextVideo();
if (!success) {
alert('No next video available');
}
};
// Initial load of playlist
loadInitialPlaylist(player);
});
async function loadInitialPlaylist(player) {
const playlist = await player.playlist.getCurrentPlaylist();
const currentVideo = await player.playlist.getCurrentVideo();
updatePlaylistDisplay(playlist);
updateCurrentVideoInfo(currentVideo);
}
function updateCurrentVideoInfo(video) {
const infoDiv = document.getElementById('current-video-info');
if (video) {
infoDiv.innerHTML = `<strong>Now Playing:</strong> ${video.title}`;
} else {
infoDiv.innerHTML = '<strong>No video selected</strong>';
}
}
function updatePlaylistDisplay(playlist) {
const listElement = document.getElementById('playlist-items');
listElement.innerHTML = '';
playlist.forEach((video, index) => {
const li = document.createElement('li');
li.innerHTML = `
<button onclick="jumpToVideo('${video.id}', ${index})">
${index + 1}. ${video.title}
</button>
`;
listElement.appendChild(li);
});
}
async function jumpToVideo(videoId, index) {
const player = window.shadowstream['your-player-id'];
if (player) {
const success = await player.playlist.jumpToVideoById(videoId);
if (!success) {
console.error('Failed to jump to video:', videoId);
}
}
}
</script>
</body>
</html>TypeScript Support
If you're using TypeScript, you can extend the Window interface to get proper type checking:
// types/shadowstream.d.ts
interface PlaylistItem {
id: string;
title: string;
url: string;
[key: string]: any;
}
interface ShadowStreamPlaylistControls {
jumpToVideoById(id: string): Promise<boolean>;
jumpToVideoByIndex(index: number): Promise<boolean>;
nextVideo(): Promise<boolean>;
previousVideo(): Promise<boolean>;
getCurrentPlaylist(): Promise<PlaylistItem[]>;
getCurrentVideo(): Promise<PlaylistItem | null>;
getCurrentIndex(): Promise<number>;
}
interface ShadowStreamPlayerInstance {
// State properties
videoElement: HTMLVideoElement;
isPlaying: boolean;
duration: number;
currentTime: number;
volume: number;
isMuted: boolean;
playlist: PlaylistItem[];
currentItem: PlaylistItem | null;
hasNext: boolean;
// Playback control methods
play(): void;
pause(): void;
togglePlayPause(): void;
seek(time: number): void;
setVolume(volume: number): void;
toggleMute(): void;
toggleFullscreen(): void;
// Navigation methods
nextVideo(): boolean;
skipVideo(): void;
// Getter methods
getCurrentTime(): number;
getDuration(): number;
getPlaylist(): PlaylistItem[];
getCurrentItem(): PlaylistItem | null;
// Playlist controls
playlist: ShadowStreamPlaylistControls;
// Event methods
addEventListener(listener: (event: any) => void): void;
removeEventListener(listener: (event: any) => void): void;
}
interface ShadowStreamGlobalAPI {
// Individual player instances (legacy pattern)
[instanceId: string]: ShadowStreamPlayerInstance;
// Centralized API methods
players: Record<string, ShadowStreamPlayerInstance>;
playlist: ShadowStreamPlaylistControls;
getPlayer(id: string): ShadowStreamPlayerInstance | undefined;
getAllPlayers(): Record<string, ShadowStreamPlayerInstance>;
pauseAll(): void;
playAll(): void;
addEventListener(type: string, listener: (event: any) => void): void;
removeEventListener(type: string, listener: (event: any) => void): void;
dispatchEvent(event: any): void;
}
declare global {
interface Window {
shadowstream?: ShadowStreamGlobalAPI;
}
}Error Handling
All playlist control methods return a Promise that resolves to a boolean indicating success:
const player = window.shadowstream['your-player-id'];
try {
const success = await player.playlist.nextVideo();
if (success) {
console.log('Successfully moved to next video');
} else {
console.log('No next video available');
}
} catch (error) {
console.error('Error navigating to next video:', error);
}Development and Debugging
In development mode, you can access the global API manager for debugging:
// Available in development mode only
console.log(window.shadowStreamAPI);
// See all registered instances
console.log(window.shadowStreamAPI.getInstanceIds());Notes
- All API methods are asynchronous and return Promises
- Event listeners are called synchronously when events occur
- The API is automatically cleaned up when the player is unmounted
- Multiple players on the same page will have different instance IDs
- Video IDs must be unique within a playlist
