UNPKG

@xnstream/player-sdk

Version:

XStream Player SDK - A powerful video player SDK for streaming content

276 lines 11.2 kB
// 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