zerorate-player
v1.3.5
Published
Custom ZeroRate HLS media player SDK with quality switching and live stream support. Specificially designed for FreePass Merchants.
Readme
ZeroratePlayer Web SDK
[BEFORE YOU INSTALL: IMPORTANT NOTICE]: This SDK is customly built to be use by merchants on the freepass.africa platform.

A robust web HLS video player SDK with built-in quality switching, live stream support, and automatic browser policy handling.
Features ✨
- 📺 HLS & MP4 video playback
- ⚡ Adaptive bitrate streaming
- 🔴 Live stream support with DVR
- 🛡️ Automatic browser policy handling
- 🎨 Customizable UI components
- 📱 Mobile-first design
- 🔄 Dynamic source switching
- 📊 Quality metrics monitoring
Installation 💻
npm install zerorate-playerQuick Start 🚀
HTML Implementation
ES Module (Recommended)
<div id="player-container"></div>
<script type="module">
import ZeroratePlayer from "zerorate-player";
import "zerorate-player/dist/zeroratePlayer.bundle.css";
const player = new ZeroratePlayer({
src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
type: "video",
mediaType: "on-demand",
autoplay: true,
videoContainerId: "player-container",
poster: "poster.jpg",
});
</script>Traditional Script Tag (UMD)
<!-- Dependencies -->
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.1.5/hls.min.js"></script>
<script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
<!-- ZeroratePlayer -->
<div id="player-container"></div>
<script src="path/to/zerorateplayer.min.js"></script>
<link rel="stylesheet" href="path/to/zeroratePlayer.bundle.css" />
<script>
document.addEventListener("DOMContentLoaded", () => {
const player = new ZeroRatePlayer({
src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
videoContainerId: "player-container",
mediaType: "on-demand",
autoplay: true,
});
});
</script>React/NextJs Implementation
import { useEffect, useRef } from "react";
import ZeroratePlayer from "zerorate-player";
import "zerorate-player/dist/zeroratePlayer.bundle.css";
export default function VideoPlayer() {
const playerRef = (useRef < ZeroratePlayer) | (null > null);
useEffect(() => {
// Initialize after component mounts
playerRef.current = new ZeroratePlayer({
appId: "your-app-id",
region: "ke-01",
src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
type: "video",
mediaType: "on-demand",
videoContainerId: "player-container",
autoplay: true,
});
// Cleanup on unmount
return () => {
if (playerRef.current) {
playerRef.current.destroy();
}
};
}, []);
return <div id="player-container" />;
}Configuration ⚙️
Constructor Options
| Option | Type | Default | Description |
| ------------------ | ------- | ------------------- | ------------------------------------------------------------- |
| appId | string | Required | Application identifier for authentication |
| region | string | Required | Service region code |
| grantType | string | "web" | Authentication grant type |
| subscriberId | string | "" | Unique subscriber identifier |
| authUrl | string | "" | Endpoint to authenticate subscriber |
| type | string | "video" | Media type ("video" or "audio") |
| src | string | Required | HLS manifest URL |
| autoplay | boolean | true | Auto-start playback |
| mediaType | string | "on-demand" | "on-demand" or "live" stream type |
| videoContainerId | string | "video-container" | DOM element ID for player container |
| poster | string | "" | Preview image URL |
| clickToPlay | boolean | false | Click overlay to start playback (auto-set based on mediaType) |
| muted | boolean | true | Initial muted state |
Key points about configuration:
clickToPlayis automatically set totruefor"on-demand"media typemediaTypeaffects available player controls- Authentication headers (
appId,region,grantType,subcriberId) are automatically added to all HLS requests - Mobile devices get
playsinlineattribute automatically
Example with authentication:
const player = new ZeroratePlayer({
appId: "your-app-id",
region: "ke-01",
subcriberId: "user-12345",
src: "https://secure-stream.m3u8",
mediaType: "live",
});API Reference 📚
Available Events
| Event Name | Trigger Condition | Event Details | Fatal? |
| -------------------- | ------------------------ | --------------------------------------------------------------------------------- | ------ |
| zerorate-sdk-error | HLS network error occurs | { status: number, message: string, url: string, fatal: boolean, response: any } | Yes |
| media-play | Playback starts/resumes | { currentTime: number, duration: number, isLive: boolean, timestamp: number } | No |
| media-pause | Playback is paused | Same as media-play | No |
| media-ended | Playback completes | Same as media-play | No |
| volume-changed | Video quality is changed | newVolumne: number | No |
| enter-fullscreen | Enters fullscreen mode | { fullscreen: true } | No |
| exit-fullscreen | Exits fullscreen mode | { fullscreen: false } | No |
Key Notes:
media-endedevent does not fire if autoplay is truefatalinzerorate-sdk-error: Whentrue, player cannot recover automaticallyresponseinzerorate-sdk-error: Contains parsed server response (if available)isLivein media events: Indicates if current stream is live content- All times are in seconds
Core Methods
// Start/resume playback
player.play();
// Pause playback
player.pause();
// Destroy player instance
player.destroy();
// Change video source
player.setSource("https://new-source.m3u8");
// Update poster image
player.setPoster("new-poster.jpg");
// Get available qualities
const qualities = player.getQualities();
// Change quality (360, 480, 720, 1080)
player.changeQuality(720);
// Toggle fullscreen
player.toggleFullscreen();Event Handling
player.on("ready", () => {
console.log("Player initialized");
});
player.on("play", () => {
console.log("Playback started");
});
player.on("qualitychange", (newQuality) => {
console.log("Quality changed to:", newQuality);
});
player.on("error", (error) => {
console.error("Player error:", error);
});
// Traditional Script Tag
document
.getElementById("player-container")
.addEventListener("zerorate-sdk-error", (e) => {
console.error("Stream Error:", e.detail);
// e.detail contains: { status: number, message: string, url: string, fatal: boolean }
});
// React/Next.js
useEffect(() => {
const videoElement = containerRef.current?.querySelector("video");
const handler = (e) => {
// Handle error state
setErrorState(e.detail);
};
videoElement?.addEventListener("zerorate-sdk-error", handler);
return () => videoElement?.removeEventListener("zerorate-sdk-error", handler);
}, []);Customization 🎨
CSS Variables
:root {
--plyr-color-main: #6a0dad;
--plyr-control-radius: 8px;
--plyr-control-spacing: 10px;
--plyr-font-family: "Inter", sans-serif;
}Quality Selector UI
const qualitySelect = document.createElement("select");
qualitySelect.className = "quality-selector";
player.on("ready", () => {
player.getQualities().forEach((quality) => {
const option = document.createElement("option");
option.value = quality;
option.text = `${quality}p`;
qualitySelect.appendChild(option);
});
qualitySelect.addEventListener("change", (e) => {
player.changeQuality(Number(e.target.value));
});
document.querySelector(".plyr__controls").appendChild(qualitySelect);
});Browser Support 🌐
| Browser | HLS | MP4 | Autoplay | Fullscreen | | ------- | --- | --- | -------- | ---------- | | Chrome | ✅ | ✅ | ✅ | ✅ | | Safari | ✅ | ✅ | ⚠️ | ✅ | | Firefox | ✅ | ✅ | ✅ | ✅ | | Edge | ✅ | ✅ | ✅ | ✅ | | Mobile | ✅ | ✅ | ⚠️ | ✅ |
Troubleshooting 🔧
Common Issues
Next.js Server-Side Rendering Error
Error: "ReferenceError: document is not defined"
Solution:
- Use dynamic import with SSR disabled:
// pages/index.js
import dynamic from "next/dynamic";
const VideoPlayer = dynamic(() => import("@/components/VideoPlayer"), {
ssr: false,
});
export default function Home() {
return <VideoPlayer />;
}- Add browser checks in your component:
// components/VideoPlayer.js
"use client"; // Required for Next.js App Router
import { useEffect, useRef } from "react";
import ZeroratePlayer from "zerorate-player";
export default function VideoPlayer() {
// ... component implementation ...
}Why this happens:
Next.js attempts server-side rendering by default, but the player requires browser APIs like document. The SDK includes built-in browser environment checks, but you still need to:
- Disable SSR for player components
- Use React's
useEffectfor initialization - Ensure client-side only execution
Other Common Issues
"Container Not Found" Error
// ❌ Wrong: Initializing before DOM ready
new ZeroratePlayer({ videoContainerId: "container" });
// ✅ Correct: Initialize in DOMContentLoaded
document.addEventListener("DOMContentLoaded", () => {
new ZeroratePlayer({ videoContainerId: "container" });
});Autoplay Blocked
- Add
muted: trueto constructor options - Initialize after user interaction:
document.addEventListener(
"click",
() => {
new ZeroratePlayer({
/* options */
});
},
{ once: true }
);Missing Styles
// Add these imports to your main JS file
import "zerorate-player/dist/zeroratePlayer.bundle.css";Common Error Handling
Handling Errors
// UMD Implementation
document
.getElementById("player-container")
.addEventListener("zerorate-sdk-error", (e) => {
console.error("Stream Error:", e.detail);
alert(`Stream Error ${e.detail.status}: ${e.detail.message}`);
});
// React Implementation
useEffect(() => {
const containerRef = (useRef < HTMLDivElement) | (null > null);
const videoElement = containerRef.current?.querySelector("video");
const errorHandler = (e: any) => {
if (e.detail.status === 403) {
alert("Stream Error " + e.detail.status + " " + e.detail.message);
}
};
containerRef.current?.addEventListener("zerorate-sdk-error", errorHandler);
return () =>
videoElement?.removeEventListener("zerorate-sdk-error", errorHandler);
}, [refreshAuth]);Contributing 🤝
- Fork the repository
- Create feature branch:
git checkout -b feature/new-component - Commit changes:
git commit -m 'Add new feature' - Push to branch:
git push origin feature/new-component - Submit pull request
License 📄
MIT License © 2023 Zerorate Team. See LICENSE for full text.
Event Handling
// Traditional HTML
const container = document.getElementById("player-container");
container.addEventListener("media-play", (e) => {
console.log("Playback started at", e.detail.currentTime);
});
container.addEventListener("media-pause", (e) => {
console.log("Paused at", e.detail.currentTime);
});
container.addEventListener("media-ended", () => {
console.log("Playback completed");
});
// React/Next.js
useEffect(() => {
const container = containerRef.current;
const playHandler = (e) => {
analytics.track("play", { time: e.detail.currentTime });
};
container?.addEventListener("media-play", playHandler);
container?.addEventListener("media-pause", handlePause);
container?.addEventListener("media-ended", handleEnd);
return () => {
container?.removeEventListener("media-play", playHandler);
container?.removeEventListener("media-pause", handlePause);
container?.removeEventListener("media-ended", handleEnd);
};
}, []);