@100mslive/hls-stats 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ import { HlsInstance, HlsPlayerStats } from '../interfaces';
2
+ export declare abstract class BaseAdapter {
3
+ hlsInstance: HlsInstance;
4
+ videoEl: HTMLVideoElement;
5
+ hlsStatsState: HlsPlayerStats;
6
+ constructor(hlsInstance: HlsInstance, videoEl: HTMLVideoElement);
7
+ abstract startGatheringStats(): void;
8
+ abstract finishGatheringStats(): void;
9
+ getState(): HlsPlayerStats;
10
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseAdapter } from './BaseAdapter';
2
+ export declare class HlsJsAdapter extends BaseAdapter {
3
+ timeUpdateHandler: () => void;
4
+ levelLoadedHandler: (_: any, { level }: {
5
+ level: number;
6
+ }) => void;
7
+ fragChangedHandler: (_: any, { frag }: {
8
+ frag: {
9
+ stats: {
10
+ bwEstimate: number;
11
+ };
12
+ baseurl: string;
13
+ };
14
+ }) => void;
15
+ startGatheringStats(): void;
16
+ finishGatheringStats(): void;
17
+ }
@@ -0,0 +1,11 @@
1
+ import { BaseAdapter } from '../adapters/BaseAdapter';
2
+ import { HlsInstance, HlsPlayerStats } from '../interfaces';
3
+ import { IHlsStats } from '../interfaces/IHlsStats';
4
+ export declare class HlsStats implements IHlsStats {
5
+ adapter: BaseAdapter;
6
+ intervalFunctionId: number;
7
+ constructor(hlsLibraryInstance: HlsInstance, videoEl: HTMLVideoElement);
8
+ subscribe(callback: (state: HlsPlayerStats) => void, interval?: number): () => void;
9
+ unsubscribe(): void;
10
+ getState(): HlsPlayerStats;
11
+ }
@@ -0,0 +1,2 @@
1
+ var u=Object.create;var n=Object.defineProperty,E=Object.defineProperties,I=Object.getOwnPropertyDescriptor,b=Object.getOwnPropertyDescriptors,g=Object.getOwnPropertyNames,p=Object.getOwnPropertySymbols,L=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable;var v=(e,t,s)=>t in e?n(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s,l=(e,t)=>{for(var s in t||(t={}))m.call(t,s)&&v(e,s,t[s]);if(p)for(var s of p(t))A.call(t,s)&&v(e,s,t[s]);return e},d=(e,t)=>E(e,b(t)),H=e=>n(e,"__esModule",{value:!0});var y=(e,t)=>{H(e);for(var s in t)n(e,s,{get:t[s],enumerable:!0})},G=(e,t,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of g(t))!m.call(e,a)&&a!=="default"&&n(e,a,{get:()=>t[a],enumerable:!(s=I(t,a))||s.enumerable});return e},F=e=>G(H(n(e!=null?u(L(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);y(exports,{HlsStats:()=>S});var r=F(require("hls.js"));var h=class{constructor(t,s){this.hlsInstance={};this.hlsStatsState={};this.hlsInstance=t,this.videoEl=s}getState(){return this.hlsStatsState}};var c=class extends h{constructor(){super(...arguments);this.timeUpdateHandler=()=>{let t=this.videoEl.buffered.length>0?this.videoEl.buffered.end(0)-this.videoEl.buffered.start(0):0,s=(this.hlsInstance.liveSyncPosition?this.hlsInstance.liveSyncPosition-this.videoEl.currentTime:0)*1e3,i=this.videoEl.getVideoPlaybackQuality().droppedVideoFrames;this.hlsStatsState=d(l({},this.hlsStatsState),{distanceFromLive:s>0?s:0,bufferedDuration:t,droppedFrames:i})};this.levelLoadedHandler=(t,{level:s})=>{let a=this.hlsInstance.levels[s],{bitrate:i,height:o,width:f}=a;this.hlsStatsState=d(l({},this.hlsStatsState),{bitrate:i,videoSize:{height:o,width:f}})};this.fragChangedHandler=(t,{frag:s})=>{let{stats:a,baseurl:i}=s,{bwEstimate:o}=a;this.hlsStatsState=d(l({},this.hlsStatsState),{bandwidthEstimate:o,url:i})}}startGatheringStats(){this.hlsInstance.on(r.default.Events.FRAG_CHANGED,this.fragChangedHandler),this.hlsInstance.on(r.default.Events.LEVEL_LOADED,this.levelLoadedHandler),this.videoEl.addEventListener("timeupdate",this.timeUpdateHandler)}finishGatheringStats(){this.videoEl.removeEventListener("timeupdate",this.timeUpdateHandler),this.hlsInstance.off(r.default.Events.FRAG_CHANGED,this.fragChangedHandler),this.hlsInstance.off(r.default.Events.LEVEL_LOADED,this.levelLoadedHandler)}};var S=class{constructor(t,s){this.intervalFunctionId=-1;let a=new c(t,s);this.adapter=a}subscribe(t,s=2e3){return this.adapter.startGatheringStats(),this.intervalFunctionId=setInterval(()=>{t(this.getState())},s),this.unsubscribe.bind(this)}unsubscribe(){clearInterval(this.intervalFunctionId),this.adapter.finishGatheringStats()}getState(){return this.adapter.getState()}};
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../src/adapters/HlsJsAdapter.ts", "../src/adapters/BaseAdapter.ts", "../src/controllers/HlsStats.ts"],
4
+ "sourcesContent": ["export { HlsStats } from './controllers/HlsStats';\nexport type { IHlsStats } from './interfaces/IHlsStats';\n", "import Hls from 'hls.js';\nimport { BaseAdapter } from './BaseAdapter';\n\nexport class HlsJsAdapter extends BaseAdapter {\n timeUpdateHandler = () => {\n const bufferedDuration =\n this.videoEl.buffered.length > 0 ? this.videoEl.buffered.end(0) - this.videoEl.buffered.start(0) : 0;\n const distanceFromLive =\n (this.hlsInstance.liveSyncPosition ? this.hlsInstance.liveSyncPosition - this.videoEl.currentTime : 0) * 1000;\n const quality = this.videoEl.getVideoPlaybackQuality();\n const droppedFrames = quality.droppedVideoFrames;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n distanceFromLive: distanceFromLive > 0 ? distanceFromLive : 0,\n bufferedDuration,\n droppedFrames,\n };\n };\n\n levelLoadedHandler = (_: any, { level }: { level: number }) => {\n const currentLevel = this.hlsInstance.levels[level];\n const { bitrate, height, width } = currentLevel;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n bitrate,\n videoSize: {\n height,\n width,\n },\n };\n };\n\n fragChangedHandler = (_: any, { frag }: { frag: { stats: { bwEstimate: number }; baseurl: string } }) => {\n const { stats, baseurl } = frag;\n const { bwEstimate } = stats;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n bandwidthEstimate: bwEstimate,\n url: baseurl,\n };\n };\n\n startGatheringStats(): void {\n this.hlsInstance.on(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);\n this.hlsInstance.on(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);\n this.videoEl.addEventListener('timeupdate', this.timeUpdateHandler);\n }\n\n finishGatheringStats(): void {\n this.videoEl.removeEventListener('timeupdate', this.timeUpdateHandler);\n this.hlsInstance.off(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);\n this.hlsInstance.off(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);\n }\n}\n", "import { HlsInstance, HlsPlayerStats } from '../interfaces';\n\nexport abstract class BaseAdapter {\n hlsInstance: HlsInstance = {} as HlsInstance;\n videoEl: HTMLVideoElement;\n hlsStatsState: HlsPlayerStats = {};\n constructor(hlsInstance: HlsInstance, videoEl: HTMLVideoElement) {\n this.hlsInstance = hlsInstance;\n this.videoEl = videoEl;\n }\n abstract startGatheringStats(): void;\n abstract finishGatheringStats(): void;\n getState() {\n return this.hlsStatsState;\n }\n}\n", "import { BaseAdapter } from '../adapters/BaseAdapter';\nimport { HlsJsAdapter } from '../adapters/HlsJsAdapter';\nimport { HlsInstance, HlsPlayerStats } from '../interfaces';\nimport { IHlsStats } from '../interfaces/IHlsStats';\n\nexport class HlsStats implements IHlsStats {\n adapter: BaseAdapter;\n intervalFunctionId = -1;\n constructor(hlsLibraryInstance: HlsInstance, videoEl: HTMLVideoElement) {\n const hlsJsAdapter = new HlsJsAdapter(hlsLibraryInstance, videoEl);\n this.adapter = hlsJsAdapter;\n }\n\n subscribe(callback: (state: HlsPlayerStats) => void, interval = 2000) {\n this.adapter.startGatheringStats();\n //@ts-ignore\n this.intervalFunctionId = setInterval(() => {\n callback(this.getState());\n }, interval);\n return this.unsubscribe.bind(this);\n }\n unsubscribe() {\n clearInterval(this.intervalFunctionId);\n this.adapter.finishGatheringStats();\n }\n\n getState() {\n return this.adapter.getState();\n }\n}\n"],
5
+ "mappings": "s6BAAA,4BCAA,MAAgB,qBCET,WAA2B,CAIhC,YAAY,EAA0B,EAA2B,CAHjE,iBAA2B,GAE3B,mBAAgC,GAE9B,KAAK,YAAc,EACnB,KAAK,QAAU,EAIjB,UAAW,CACT,MAAO,MAAK,gBDVT,mBAA2B,EAAY,CAAvC,aAHP,CAGO,oBACL,uBAAoB,IAAM,CACxB,GAAM,GACJ,KAAK,QAAQ,SAAS,OAAS,EAAI,KAAK,QAAQ,SAAS,IAAI,GAAK,KAAK,QAAQ,SAAS,MAAM,GAAK,EAC/F,EACH,MAAK,YAAY,iBAAmB,KAAK,YAAY,iBAAmB,KAAK,QAAQ,YAAc,GAAK,IAErG,EAAgB,AADN,KAAK,QAAQ,0BACC,mBAC9B,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,iBAAkB,EAAmB,EAAI,EAAmB,EAC5D,mBACA,mBAIJ,wBAAqB,CAAC,EAAQ,CAAE,WAA+B,CAC7D,GAAM,GAAe,KAAK,YAAY,OAAO,GACvC,CAAE,UAAS,SAAQ,SAAU,EACnC,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,UACA,UAAW,CACT,SACA,YAKN,wBAAqB,CAAC,EAAQ,CAAE,UAAyE,CACvG,GAAM,CAAE,QAAO,WAAY,EACrB,CAAE,cAAe,EACvB,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,kBAAmB,EACnB,IAAK,KAIT,qBAA4B,CAC1B,KAAK,YAAY,GAAG,UAAI,OAAO,aAAc,KAAK,oBAClD,KAAK,YAAY,GAAG,UAAI,OAAO,aAAc,KAAK,oBAClD,KAAK,QAAQ,iBAAiB,aAAc,KAAK,mBAGnD,sBAA6B,CAC3B,KAAK,QAAQ,oBAAoB,aAAc,KAAK,mBACpD,KAAK,YAAY,IAAI,UAAI,OAAO,aAAc,KAAK,oBACnD,KAAK,YAAY,IAAI,UAAI,OAAO,aAAc,KAAK,sBE9ChD,WAAoC,CAGzC,YAAY,EAAiC,EAA2B,CADxE,wBAAqB,GAEnB,GAAM,GAAe,GAAI,GAAa,EAAoB,GAC1D,KAAK,QAAU,EAGjB,UAAU,EAA2C,EAAW,IAAM,CACpE,YAAK,QAAQ,sBAEb,KAAK,mBAAqB,YAAY,IAAM,CAC1C,EAAS,KAAK,aACb,GACI,KAAK,YAAY,KAAK,MAE/B,aAAc,CACZ,cAAc,KAAK,oBACnB,KAAK,QAAQ,uBAGf,UAAW,CACT,MAAO,MAAK,QAAQ",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export { HlsStats } from './controllers/HlsStats';
2
+ export type { IHlsStats } from './interfaces/IHlsStats';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var v=Object.defineProperty,H=Object.defineProperties;var f=Object.getOwnPropertyDescriptors;var c=Object.getOwnPropertySymbols;var u=Object.prototype.hasOwnProperty,E=Object.prototype.propertyIsEnumerable;var S=(s,e,t)=>e in s?v(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,n=(s,e)=>{for(var t in e||(e={}))u.call(e,t)&&S(s,t,e[t]);if(c)for(var t of c(e))E.call(e,t)&&S(s,t,e[t]);return s},r=(s,e)=>H(s,f(e));import l from"hls.js";var o=class{constructor(e,t){this.hlsInstance={};this.hlsStatsState={};this.hlsInstance=e,this.videoEl=t}getState(){return this.hlsStatsState}};var h=class extends o{constructor(){super(...arguments);this.timeUpdateHandler=()=>{let e=this.videoEl.buffered.length>0?this.videoEl.buffered.end(0)-this.videoEl.buffered.start(0):0,t=(this.hlsInstance.liveSyncPosition?this.hlsInstance.liveSyncPosition-this.videoEl.currentTime:0)*1e3,i=this.videoEl.getVideoPlaybackQuality().droppedVideoFrames;this.hlsStatsState=r(n({},this.hlsStatsState),{distanceFromLive:t>0?t:0,bufferedDuration:e,droppedFrames:i})};this.levelLoadedHandler=(e,{level:t})=>{let a=this.hlsInstance.levels[t],{bitrate:i,height:d,width:m}=a;this.hlsStatsState=r(n({},this.hlsStatsState),{bitrate:i,videoSize:{height:d,width:m}})};this.fragChangedHandler=(e,{frag:t})=>{let{stats:a,baseurl:i}=t,{bwEstimate:d}=a;this.hlsStatsState=r(n({},this.hlsStatsState),{bandwidthEstimate:d,url:i})}}startGatheringStats(){this.hlsInstance.on(l.Events.FRAG_CHANGED,this.fragChangedHandler),this.hlsInstance.on(l.Events.LEVEL_LOADED,this.levelLoadedHandler),this.videoEl.addEventListener("timeupdate",this.timeUpdateHandler)}finishGatheringStats(){this.videoEl.removeEventListener("timeupdate",this.timeUpdateHandler),this.hlsInstance.off(l.Events.FRAG_CHANGED,this.fragChangedHandler),this.hlsInstance.off(l.Events.LEVEL_LOADED,this.levelLoadedHandler)}};var p=class{constructor(e,t){this.intervalFunctionId=-1;let a=new h(e,t);this.adapter=a}subscribe(e,t=2e3){return this.adapter.startGatheringStats(),this.intervalFunctionId=setInterval(()=>{e(this.getState())},t),this.unsubscribe.bind(this)}unsubscribe(){clearInterval(this.intervalFunctionId),this.adapter.finishGatheringStats()}getState(){return this.adapter.getState()}};export{p as HlsStats};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/adapters/HlsJsAdapter.ts", "../src/adapters/BaseAdapter.ts", "../src/controllers/HlsStats.ts"],
4
+ "sourcesContent": ["import Hls from 'hls.js';\nimport { BaseAdapter } from './BaseAdapter';\n\nexport class HlsJsAdapter extends BaseAdapter {\n timeUpdateHandler = () => {\n const bufferedDuration =\n this.videoEl.buffered.length > 0 ? this.videoEl.buffered.end(0) - this.videoEl.buffered.start(0) : 0;\n const distanceFromLive =\n (this.hlsInstance.liveSyncPosition ? this.hlsInstance.liveSyncPosition - this.videoEl.currentTime : 0) * 1000;\n const quality = this.videoEl.getVideoPlaybackQuality();\n const droppedFrames = quality.droppedVideoFrames;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n distanceFromLive: distanceFromLive > 0 ? distanceFromLive : 0,\n bufferedDuration,\n droppedFrames,\n };\n };\n\n levelLoadedHandler = (_: any, { level }: { level: number }) => {\n const currentLevel = this.hlsInstance.levels[level];\n const { bitrate, height, width } = currentLevel;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n bitrate,\n videoSize: {\n height,\n width,\n },\n };\n };\n\n fragChangedHandler = (_: any, { frag }: { frag: { stats: { bwEstimate: number }; baseurl: string } }) => {\n const { stats, baseurl } = frag;\n const { bwEstimate } = stats;\n this.hlsStatsState = {\n ...this.hlsStatsState,\n bandwidthEstimate: bwEstimate,\n url: baseurl,\n };\n };\n\n startGatheringStats(): void {\n this.hlsInstance.on(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);\n this.hlsInstance.on(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);\n this.videoEl.addEventListener('timeupdate', this.timeUpdateHandler);\n }\n\n finishGatheringStats(): void {\n this.videoEl.removeEventListener('timeupdate', this.timeUpdateHandler);\n this.hlsInstance.off(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);\n this.hlsInstance.off(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);\n }\n}\n", "import { HlsInstance, HlsPlayerStats } from '../interfaces';\n\nexport abstract class BaseAdapter {\n hlsInstance: HlsInstance = {} as HlsInstance;\n videoEl: HTMLVideoElement;\n hlsStatsState: HlsPlayerStats = {};\n constructor(hlsInstance: HlsInstance, videoEl: HTMLVideoElement) {\n this.hlsInstance = hlsInstance;\n this.videoEl = videoEl;\n }\n abstract startGatheringStats(): void;\n abstract finishGatheringStats(): void;\n getState() {\n return this.hlsStatsState;\n }\n}\n", "import { BaseAdapter } from '../adapters/BaseAdapter';\nimport { HlsJsAdapter } from '../adapters/HlsJsAdapter';\nimport { HlsInstance, HlsPlayerStats } from '../interfaces';\nimport { IHlsStats } from '../interfaces/IHlsStats';\n\nexport class HlsStats implements IHlsStats {\n adapter: BaseAdapter;\n intervalFunctionId = -1;\n constructor(hlsLibraryInstance: HlsInstance, videoEl: HTMLVideoElement) {\n const hlsJsAdapter = new HlsJsAdapter(hlsLibraryInstance, videoEl);\n this.adapter = hlsJsAdapter;\n }\n\n subscribe(callback: (state: HlsPlayerStats) => void, interval = 2000) {\n this.adapter.startGatheringStats();\n //@ts-ignore\n this.intervalFunctionId = setInterval(() => {\n callback(this.getState());\n }, interval);\n return this.unsubscribe.bind(this);\n }\n unsubscribe() {\n clearInterval(this.intervalFunctionId);\n this.adapter.finishGatheringStats();\n }\n\n getState() {\n return this.adapter.getState();\n }\n}\n"],
5
+ "mappings": "6aAAA,sBCEO,WAA2B,CAIhC,YAAY,EAA0B,EAA2B,CAHjE,iBAA2B,GAE3B,mBAAgC,GAE9B,KAAK,YAAc,EACnB,KAAK,QAAU,EAIjB,UAAW,CACT,MAAO,MAAK,gBDVT,mBAA2B,EAAY,CAAvC,aAHP,CAGO,oBACL,uBAAoB,IAAM,CACxB,GAAM,GACJ,KAAK,QAAQ,SAAS,OAAS,EAAI,KAAK,QAAQ,SAAS,IAAI,GAAK,KAAK,QAAQ,SAAS,MAAM,GAAK,EAC/F,EACH,MAAK,YAAY,iBAAmB,KAAK,YAAY,iBAAmB,KAAK,QAAQ,YAAc,GAAK,IAErG,EAAgB,AADN,KAAK,QAAQ,0BACC,mBAC9B,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,iBAAkB,EAAmB,EAAI,EAAmB,EAC5D,mBACA,mBAIJ,wBAAqB,CAAC,EAAQ,CAAE,WAA+B,CAC7D,GAAM,GAAe,KAAK,YAAY,OAAO,GACvC,CAAE,UAAS,SAAQ,SAAU,EACnC,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,UACA,UAAW,CACT,SACA,YAKN,wBAAqB,CAAC,EAAQ,CAAE,UAAyE,CACvG,GAAM,CAAE,QAAO,WAAY,EACrB,CAAE,cAAe,EACvB,KAAK,cAAgB,OAChB,KAAK,eADW,CAEnB,kBAAmB,EACnB,IAAK,KAIT,qBAA4B,CAC1B,KAAK,YAAY,GAAG,EAAI,OAAO,aAAc,KAAK,oBAClD,KAAK,YAAY,GAAG,EAAI,OAAO,aAAc,KAAK,oBAClD,KAAK,QAAQ,iBAAiB,aAAc,KAAK,mBAGnD,sBAA6B,CAC3B,KAAK,QAAQ,oBAAoB,aAAc,KAAK,mBACpD,KAAK,YAAY,IAAI,EAAI,OAAO,aAAc,KAAK,oBACnD,KAAK,YAAY,IAAI,EAAI,OAAO,aAAc,KAAK,sBE9ChD,WAAoC,CAGzC,YAAY,EAAiC,EAA2B,CADxE,wBAAqB,GAEnB,GAAM,GAAe,GAAI,GAAa,EAAoB,GAC1D,KAAK,QAAU,EAGjB,UAAU,EAA2C,EAAW,IAAM,CACpE,YAAK,QAAQ,sBAEb,KAAK,mBAAqB,YAAY,IAAM,CAC1C,EAAS,KAAK,aACb,GACI,KAAK,YAAY,KAAK,MAE/B,aAAc,CACZ,cAAc,KAAK,oBACnB,KAAK,QAAQ,uBAGf,UAAW,CACT,MAAO,MAAK,QAAQ",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ import { HlsPlayerStats } from '.';
2
+ export interface IHlsStats {
3
+ subscribe(callback: (state: HlsPlayerStats) => void, interval: number): () => void;
4
+ getState(): HlsPlayerStats;
5
+ }
@@ -0,0 +1,25 @@
1
+ import Hls from 'hls.js';
2
+ /**
3
+ * this is a universal type to be passed for BaseAdapter.
4
+ * Union more Types to this type as we support different libraries.
5
+ */
6
+ export declare type HlsInstance = Hls;
7
+ export interface HlsPlayerStats {
8
+ /** Estimated bandwidth in bits/sec. Could be used to show connection speed. */
9
+ bandwidthEstimate?: number;
10
+ /** The bitrate of the current level that is playing. Given in bits/sec */
11
+ bitrate?: number;
12
+ /** the amount of video available in forward buffer. Given in ms */
13
+ bufferedDuration?: number;
14
+ /** how far is the current playback from live edge.*/
15
+ distanceFromLive?: number;
16
+ /** total Frames dropped since started watching the stream. */
17
+ droppedFrames?: number;
18
+ /** the m3u8 url of the current level that is being played */
19
+ url?: string;
20
+ /** the resolution of the level of the video that is being played */
21
+ videoSize?: {
22
+ height: number;
23
+ width: number;
24
+ };
25
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@100mslive/hls-stats",
3
+ "version": "0.0.1",
4
+ "description": "A simple library that provides stats for your hls stream",
5
+ "main": "dist/index.cjs.js",
6
+ "module": "dist/index.js",
7
+ "typings": "dist/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "dist"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "require": "./dist/index.cjs.js",
15
+ "import": "./dist/index.js",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "prestart": "rm -rf dist && yarn types:build",
21
+ "start": "concurrently \"yarn dev\" \"yarn types\"",
22
+ "dev": "node ../../scripts/dev",
23
+ "build:only": "node ../../scripts/build",
24
+ "prebuild": "rm -rf dist",
25
+ "build": "yarn build:only && yarn types:build",
26
+ "types": "tsc -w",
27
+ "types:build": "tsc -p tsconfig.json",
28
+ "lint": "eslint -c ../../.eslintrc .",
29
+ "lint:fix": "yarn lint --fix"
30
+ },
31
+ "author": "Ragzzy-R",
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "hls.js": "^1.2.5"
35
+ },
36
+ "peerDependencies": {
37
+ "hls.js": "^1.2.5"
38
+ }
39
+ }
@@ -0,0 +1,16 @@
1
+ import { HlsInstance, HlsPlayerStats } from '../interfaces';
2
+
3
+ export abstract class BaseAdapter {
4
+ hlsInstance: HlsInstance = {} as HlsInstance;
5
+ videoEl: HTMLVideoElement;
6
+ hlsStatsState: HlsPlayerStats = {};
7
+ constructor(hlsInstance: HlsInstance, videoEl: HTMLVideoElement) {
8
+ this.hlsInstance = hlsInstance;
9
+ this.videoEl = videoEl;
10
+ }
11
+ abstract startGatheringStats(): void;
12
+ abstract finishGatheringStats(): void;
13
+ getState() {
14
+ return this.hlsStatsState;
15
+ }
16
+ }
@@ -0,0 +1,54 @@
1
+ import Hls from 'hls.js';
2
+ import { BaseAdapter } from './BaseAdapter';
3
+
4
+ export class HlsJsAdapter extends BaseAdapter {
5
+ timeUpdateHandler = () => {
6
+ const bufferedDuration =
7
+ this.videoEl.buffered.length > 0 ? this.videoEl.buffered.end(0) - this.videoEl.buffered.start(0) : 0;
8
+ const distanceFromLive =
9
+ (this.hlsInstance.liveSyncPosition ? this.hlsInstance.liveSyncPosition - this.videoEl.currentTime : 0) * 1000;
10
+ const quality = this.videoEl.getVideoPlaybackQuality();
11
+ const droppedFrames = quality.droppedVideoFrames;
12
+ this.hlsStatsState = {
13
+ ...this.hlsStatsState,
14
+ distanceFromLive: distanceFromLive > 0 ? distanceFromLive : 0,
15
+ bufferedDuration,
16
+ droppedFrames,
17
+ };
18
+ };
19
+
20
+ levelLoadedHandler = (_: any, { level }: { level: number }) => {
21
+ const currentLevel = this.hlsInstance.levels[level];
22
+ const { bitrate, height, width } = currentLevel;
23
+ this.hlsStatsState = {
24
+ ...this.hlsStatsState,
25
+ bitrate,
26
+ videoSize: {
27
+ height,
28
+ width,
29
+ },
30
+ };
31
+ };
32
+
33
+ fragChangedHandler = (_: any, { frag }: { frag: { stats: { bwEstimate: number }; baseurl: string } }) => {
34
+ const { stats, baseurl } = frag;
35
+ const { bwEstimate } = stats;
36
+ this.hlsStatsState = {
37
+ ...this.hlsStatsState,
38
+ bandwidthEstimate: bwEstimate,
39
+ url: baseurl,
40
+ };
41
+ };
42
+
43
+ startGatheringStats(): void {
44
+ this.hlsInstance.on(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);
45
+ this.hlsInstance.on(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);
46
+ this.videoEl.addEventListener('timeupdate', this.timeUpdateHandler);
47
+ }
48
+
49
+ finishGatheringStats(): void {
50
+ this.videoEl.removeEventListener('timeupdate', this.timeUpdateHandler);
51
+ this.hlsInstance.off(Hls.Events.FRAG_CHANGED, this.fragChangedHandler);
52
+ this.hlsInstance.off(Hls.Events.LEVEL_LOADED, this.levelLoadedHandler);
53
+ }
54
+ }
@@ -0,0 +1,30 @@
1
+ import { BaseAdapter } from '../adapters/BaseAdapter';
2
+ import { HlsJsAdapter } from '../adapters/HlsJsAdapter';
3
+ import { HlsInstance, HlsPlayerStats } from '../interfaces';
4
+ import { IHlsStats } from '../interfaces/IHlsStats';
5
+
6
+ export class HlsStats implements IHlsStats {
7
+ adapter: BaseAdapter;
8
+ intervalFunctionId = -1;
9
+ constructor(hlsLibraryInstance: HlsInstance, videoEl: HTMLVideoElement) {
10
+ const hlsJsAdapter = new HlsJsAdapter(hlsLibraryInstance, videoEl);
11
+ this.adapter = hlsJsAdapter;
12
+ }
13
+
14
+ subscribe(callback: (state: HlsPlayerStats) => void, interval = 2000) {
15
+ this.adapter.startGatheringStats();
16
+ //@ts-ignore
17
+ this.intervalFunctionId = setInterval(() => {
18
+ callback(this.getState());
19
+ }, interval);
20
+ return this.unsubscribe.bind(this);
21
+ }
22
+ unsubscribe() {
23
+ clearInterval(this.intervalFunctionId);
24
+ this.adapter.finishGatheringStats();
25
+ }
26
+
27
+ getState() {
28
+ return this.adapter.getState();
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { HlsStats } from './controllers/HlsStats';
2
+ export type { IHlsStats } from './interfaces/IHlsStats';
@@ -0,0 +1,6 @@
1
+ import { HlsPlayerStats } from '.';
2
+
3
+ export interface IHlsStats {
4
+ subscribe(callback: (state: HlsPlayerStats) => void, interval: number): () => void;
5
+ getState(): HlsPlayerStats;
6
+ }
@@ -0,0 +1,24 @@
1
+ import Hls from 'hls.js';
2
+
3
+ /**
4
+ * this is a universal type to be passed for BaseAdapter.
5
+ * Union more Types to this type as we support different libraries.
6
+ */
7
+ export type HlsInstance = Hls;
8
+
9
+ export interface HlsPlayerStats {
10
+ /** Estimated bandwidth in bits/sec. Could be used to show connection speed. */
11
+ bandwidthEstimate?: number;
12
+ /** The bitrate of the current level that is playing. Given in bits/sec */
13
+ bitrate?: number;
14
+ /** the amount of video available in forward buffer. Given in ms */
15
+ bufferedDuration?: number;
16
+ /** how far is the current playback from live edge.*/
17
+ distanceFromLive?: number;
18
+ /** total Frames dropped since started watching the stream. */
19
+ droppedFrames?: number;
20
+ /** the m3u8 url of the current level that is being played */
21
+ url?: string;
22
+ /** the resolution of the level of the video that is being played */
23
+ videoSize?: { height: number; width: number };
24
+ }