@xnstream/player-sdk
Version:
XStream Player SDK - A powerful video player SDK for streaming content
276 lines • 11.2 kB
JavaScript
// Use global Hls when running in browser, otherwise import from module
import Hls from 'hls.js';
import { AnalyticsCollector } from './analytics/AnalyticsCollector';
import { SessionRegistration } from './device/SessionRegistration';
import { fetchMediaSession } from './utils/mediaSession';
import { createFragmentLoader } from './hls/FragmentLoader';
export class StreamPlayer {
constructor(options, sessionRegistration) {
this.timer = null;
this.qualityLevels = [];
this.options = options;
this.streamCode = options.stream_code;
this.sessionRegistration = sessionRegistration;
// Get container element
const container = document.getElementById(options.containerId);
if (!container) {
throw new Error(`Container element with id "${options.containerId}" not found`);
}
this.container = container;
// Create video element
this.videoElement = document.createElement('video');
if (options.autoPlay)
this.videoElement.setAttribute('autoplay', '');
this.videoElement.setAttribute('playsinline', '');
this.videoElement.setAttribute('webkit-playsinline', '');
this.videoElement.style.width = '100%';
this.videoElement.style.height = '100%';
this.videoElement.style.minHeight = '100%';
this.videoElement.style.minWidth = '100%';
this.container.appendChild(this.videoElement);
this.eventListeners = new Map();
// Initialize HLS.js with default config
const defaultConfig = {
enableWorker: true,
lowLatencyMode: false,
backBufferLength: 10,
maxBufferLength: 60,
maxMaxBufferLength: 120,
highBufferWatchdogPeriod: 3,
abrMaxWithRealBitrate: true,
};
this.hls = new Hls(defaultConfig);
}
static async create(options) {
const sessionRegistration = await SessionRegistration.create(options.appId);
return new StreamPlayer(options, sessionRegistration);
}
async initialize() {
var _a;
try {
// Try to get existing session
let sessionId = await this.sessionRegistration.getSession();
if (sessionId) {
// Fetch resource with existing session
await this.sessionRegistration.setAps();
const { resource, sid } = await this.sessionRegistration.fetchResource(this.streamCode, StreamPlayer.BASE_URL);
if (sid)
sessionId = sid;
this.resource = resource;
}
else {
const newSession = await this.sessionRegistration.registerWithServer(this.streamCode, StreamPlayer.BASE_URL);
sessionId = newSession.sid;
this.resource = newSession.resource;
}
if (this.resource) {
// Fetch media session
this.emit('onResourceChange', this.resource);
this.setVideoPoster(this.resource);
const mediaSession = await fetchMediaSession(StreamPlayer.BASE_URL, {
app_id: this.sessionRegistration.getAppId(),
sid: sessionId,
rs: this.streamCode,
device: await this.sessionRegistration.getDeviceInfo(),
slo: this.getDocumentReferer(),
}, (_a = this.sessionRegistration.getAps()) !== null && _a !== void 0 ? _a : '');
if (mediaSession) {
// Create HLS.js with custom fragment loader using media session signature
const baseConfig = {
enableWorker: true,
lowLatencyMode: true,
backBufferLength: 90,
};
const hlsConfig = {
...baseConfig,
loader: createFragmentLoader(baseConfig, mediaSession.sig),
};
this.hls = new Hls(hlsConfig);
this.setupHlsEvents();
this.setupVideoEvents();
// Load the stream URL
this.edge_id = mediaSession.host;
// Initialize analytics with device info
const deviceInfo = await this.sessionRegistration.getDeviceInfo();
if (!this.edge_id) {
throw new Error('Edge ID not available for analytics');
}
this.analyticsCollector = new AnalyticsCollector({
host: StreamPlayer.BASE_URL,
sid: sessionId,
hlsPlayer: this.hls,
video: this.videoElement,
appId: this.options.appId,
stream_code: this.streamCode,
device: deviceInfo,
resource: this.resource,
edge_id: this.edge_id,
pb_id: mediaSession.sig,
appVersion: StreamPlayer.VERSION,
userContext: this.options.context,
});
await this.load(mediaSession.host + mediaSession.path);
}
}
}
catch (error) {
this.emit('error', error instanceof Error ? error : new Error('Failed to initialize player'));
throw error;
}
}
setupHlsEvents() {
this.hls.on(Hls.Events.ERROR, (_event, data) => {
if (data.fatal) {
this.emit('error', new Error(`HLS Error: ${data.type}`));
}
});
this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
this.qualityLevels = data.levels.map(level => {
return {
id: level.id,
bitrate: level.bitrate,
width: level.width,
height: level.height,
frameRate: level.frameRate,
videoCodec: level.videoCodec,
audioCodec: level.audioCodec,
};
});
this.emit('onLevelsLoaded', this.qualityLevels);
});
this.hls.on(Hls.Events.LEVEL_SWITCHED, (_event, data) => {
this.emit('onLevelSwitch', data.level);
});
this.hls.on(Hls.Events.BUFFER_APPENDED, (_event, data) => {
if (data.type == 'video' || data.type == 'audiovideo')
this.emit('onBufferUpdated', data);
});
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.emit('ready');
this.startTimer();
});
}
setupVideoEvents() {
this.videoElement.addEventListener('play', () => this.emit('playing'));
this.videoElement.addEventListener('playing', () => this.emit('playing'));
this.videoElement.addEventListener('pause', () => this.emit('paused'));
this.videoElement.addEventListener('ended', () => this.emit('ended'));
this.videoElement.addEventListener('stalled', () => this.emit('buffering'));
this.videoElement.addEventListener('waiting', () => this.emit('buffering'));
this.videoElement.addEventListener('loadeddata', () => this.emit('onDataLoaded', this.videoElement.duration));
this.videoElement.addEventListener('volumechange', () => this.emit('volumechange', this.videoElement.volume));
}
startTimer() {
if (this.timer)
clearInterval(this.timer);
this.timer = setInterval(() => this.updateProgress(), 300);
}
cancelTimer() {
if (this.timer)
clearInterval(this.timer);
}
updateProgress() {
this.emit('onProgressUpdate', {
position: this.videoElement.currentTime,
seekStart: this.videoElement.seekable.length > 0 ? this.videoElement.seekable.start(0) : 0,
seekEnd: this.videoElement.seekable.length > 0 ? this.videoElement.seekable.end(0) : 0,
latency: this.hls.latency,
targetLatency: this.hls.targetLatency,
});
}
setVideoPoster(resource) {
var _a, _b;
if (resource.type === 'live') {
this.videoElement.poster = resource.resource.poster.original;
}
else if (resource.type === 'vod') {
this.videoElement.poster = (_b = (_a = resource.resource.thumbnail) === null || _a === void 0 ? void 0 : _a.cover) !== null && _b !== void 0 ? _b : '';
}
}
async load(streamUrl) {
if (Hls.isSupported()) {
this.hls.loadSource(streamUrl);
this.hls.attachMedia(this.videoElement);
}
else if (this.videoElement.canPlayType('application/vnd.apple.mpegurl')) {
this.videoElement.src = streamUrl;
}
else {
throw new Error('HLS is not supported in this browser');
}
}
play() {
return this.videoElement.play();
}
rewind(offset = 10) {
this.videoElement.currentTime -= offset;
}
fastForward(offset = 10) {
this.videoElement.currentTime += offset;
}
pause() {
this.videoElement.pause();
}
seek(time) {
this.videoElement.currentTime = time;
}
toggleMute() {
this.videoElement.muted = !this.videoElement.muted;
}
switchLevel(level) {
this.hls.currentLevel = level;
}
seekToLive() {
var _a;
this.videoElement.currentTime = (_a = this.hls.liveSyncPosition) !== null && _a !== void 0 ? _a : this.videoElement.currentTime;
}
setVolume(volume) {
this.videoElement.volume = volume;
}
getVolume() {
return this.videoElement.volume;
}
isMuted() {
return this.videoElement.muted;
}
isAutolevelEnabled() {
return this.hls.autoLevelEnabled;
}
getLevels() {
return this.qualityLevels;
}
getCurrentLevel() {
return this.hls.currentLevel;
}
on(event, callback) {
var _a;
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, new Set());
}
(_a = this.eventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.add(callback);
}
off(event, callback) {
var _a;
(_a = this.eventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.delete(callback);
}
emit(event, ...args) {
var _a;
(_a = this.eventListeners.get(event)) === null || _a === void 0 ? void 0 : _a.forEach(callback => callback(...args));
}
destroy() {
this.cancelTimer();
this.hls.destroy();
this.analyticsCollector.destroy();
this.eventListeners.clear();
this.videoElement.remove();
}
getDocumentReferer() {
let referer = document.referrer;
if (referer === '' || !referer)
referer = document.location.href;
return referer;
}
}
StreamPlayer.BASE_URL = 'https://api.novastream.et/api/v1';
StreamPlayer.VERSION = '1.0.8';
//# sourceMappingURL=StreamPlayer.js.map