@ngx-telly/plugin-hls
Version:
Telly - HLS plugin
173 lines (169 loc) • 7.43 kB
JavaScript
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();
this.config = input({});
this.scte = input();
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: "19.2.9", ngImport: i0, type: HlsDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.9", 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: "19.2.9", ngImport: i0, type: HlsDirective, decorators: [{
type: Directive,
args: [{
selector: '[tellyHls]',
standalone: true,
}]
}], propDecorators: { 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