UNPKG

@ngx-telly/plugin-hls

Version:
173 lines (169 loc) 7.95 kB
import * as i0 from '@angular/core'; import { input, EventEmitter, inject, Output, Directive } from '@angular/core'; import { PlayerComponent } from '@ngx-telly/player'; import Hls from 'hls.js'; class HlsDirective { constructor() { this.tellyHls = input.required(...(ngDevMode ? [{ debugName: "tellyHls" }] : /* istanbul ignore next */ [])); this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ [])); this.scte = input(...(ngDevMode ? [undefined, { debugName: "scte" }] : /* istanbul ignore next */ [])); this.hlsCreated = new EventEmitter(); this.mediaAttached = new EventEmitter(); this.manifestParsed = new EventEmitter(); this.levelUpdated = new EventEmitter(); this.offsetParsed = new EventEmitter(); this.audioSwitched = new EventEmitter(); this.levelSwitched = new EventEmitter(); this.captionSwitched = new EventEmitter(); this.scteParsed = new EventEmitter(); this.manifestFailed = new EventEmitter(); this.player = inject(PlayerComponent); this.offset = 0; this.timestamp = 0; } ngAfterViewInit() { if (!this.hls && this.tellyHls()) { this.create(this.tellyHls()); } } ngOnChanges({ tellyHls }) { if (tellyHls && tellyHls.currentValue && !tellyHls.isFirstChange()) { this.create(tellyHls.currentValue); } } date() { return new Date(this.timestamp + this.player.media.time * 1000); } create(url) { if (!url) return; if (Hls.isSupported()) { if (this.hls) { this.hls.destroy(); } const config = this.config(); config.autoStartLoad ??= this.player.preload() === 'auto'; config.startPosition ??= this.player.startFrom(); config.xhrSetup ??= (xhr) => { xhr.withCredentials = this.player.crossOrigin() === 'use-credentials'; }; this.hls = new Hls(config); this.hlsCreated.emit(this.hls); this.hls.loadSource(url); this.hls.attachMedia(this.player.video.nativeElement); this.bindEvents(); } else if (this.player.video.nativeElement.canPlayType('application/vnd.apple.mpegurl')) { this.player.video.nativeElement.src = url; } } bindEvents() { this.hls?.on(Hls.Events.MEDIA_ATTACHED, (_, data) => { this.mediaAttached.emit([this.hls, data]); }); this.hls?.on(Hls.Events.MANIFEST_PARSED, (_, data) => { this.offset = 0; this.timestamp = 0; this.offsetParsed.emit([this.hls, this.offset, this.timestamp]); this.manifestParsed.emit([this.hls, data]); }); this.hls?.on(Hls.Events.LEVEL_UPDATED, (_, data) => { this.levelUpdated.emit([this.hls, data]); const level = this.hls?.levels[data.level]; if (level != null) { this.extractDuration(level); this.extractDrift(level); this.extractOffset(level); if (this.scte()) { this.extractScte(level); } } }); this.hls?.on(Hls.Events.LEVEL_SWITCHED, (_, data) => { const level = this.hls?.levels[data.level]; if (level != null) this.extractDuration(level); this.levelSwitched.emit([this.hls, data]); }); this.hls?.on(Hls.Events.AUDIO_TRACK_SWITCHED, (_, data) => { this.audioSwitched.emit([this.hls, data]); }); this.hls?.on(Hls.Events.SUBTITLE_TRACK_SWITCH, (_, data) => { this.captionSwitched.emit([this.hls, data]); }); this.hls?.on(Hls.Events.ERROR, (_, data) => { if (data.details === Hls.ErrorDetails.MANIFEST_LOAD_ERROR) { this.manifestFailed.emit([this.hls, data]); } }); } extractDuration(lvl) { if (lvl.details?.totalduration != null && !isNaN(lvl.details.totalduration)) { this.player.media.duration = lvl.details.totalduration; } } extractDrift(lvl) { this.player.media.drift = lvl.details?.fragments[0].start ?? this.player.media.drift; } extractOffset(lvl) { if (!this.offset && lvl.details) { this.timestamp = lvl.details?.fragments[0].programDateTime ?? 0; this.offset = (this.timestamp / 1000) % 86400; this.offsetParsed.emit([this.hls, this.offset, this.timestamp]); } } extractScte(lvl) { const frags = lvl.details?.fragments.filter((x) => x.tagList.some((x) => x[0].includes('CUE'))); if (!frags) return; const parsed = frags.map((f) => { const tags = Object.fromEntries(f.tagList); return { timestamp: new Date(tags['PROGRAM-DATE-TIME']).getTime(), cueIn: Object.keys(tags).includes('EXT-X-CUE-IN'), cueOut: Object.keys(tags).includes('EXT-X-CUE-OUT'), duration: tags['EXT-X-CUE-OUT'] != null ? +tags['EXT-X-CUE-OUT'] : undefined, data: tags[this.scte()], dateTime: tags['PROGRAM-DATE-TIME'], }; }); this.scteParsed.emit([this.hls, parsed]); } ngOnDestroy() { this.hls?.destroy(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HlsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.16", type: HlsDirective, isStandalone: true, selector: "[tellyHls]", inputs: { tellyHls: { classPropertyName: "tellyHls", publicName: "tellyHls", isSignal: true, isRequired: true, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, scte: { classPropertyName: "scte", publicName: "scte", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { hlsCreated: "hlsCreated", mediaAttached: "mediaAttached", manifestParsed: "manifestParsed", levelUpdated: "levelUpdated", offsetParsed: "offsetParsed", audioSwitched: "audioSwitched", levelSwitched: "levelSwitched", captionSwitched: "captionSwitched", scteParsed: "scteParsed", manifestFailed: "manifestFailed" }, usesOnChanges: true, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.16", ngImport: i0, type: HlsDirective, decorators: [{ type: Directive, args: [{ selector: '[tellyHls]', standalone: true, }] }], propDecorators: { tellyHls: [{ type: i0.Input, args: [{ isSignal: true, alias: "tellyHls", required: true }] }], config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], scte: [{ type: i0.Input, args: [{ isSignal: true, alias: "scte", required: false }] }], hlsCreated: [{ type: Output }], mediaAttached: [{ type: Output }], manifestParsed: [{ type: Output }], levelUpdated: [{ type: Output }], offsetParsed: [{ type: Output }], audioSwitched: [{ type: Output }], levelSwitched: [{ type: Output }], captionSwitched: [{ type: Output }], scteParsed: [{ type: Output }], manifestFailed: [{ type: Output }] } }); /** * Generated bundle index. Do not edit. */ export { HlsDirective }; //# sourceMappingURL=ngx-telly-plugin-hls.mjs.map