@js-toolkit/web-utils 1.66.0 → 1.68.0

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.
Files changed (120) hide show
  1. package/EventEmitterListener.d.ts +3 -3
  2. package/EventEmitterListener.js +222 -1
  3. package/EventEmitterListener.utils.d.ts +24 -13
  4. package/EventEmitterListener.utils.js +41 -1
  5. package/EventListeners.js +58 -1
  6. package/FullscreenController.js +193 -1
  7. package/README.md +159 -20
  8. package/WakeLockController.js +76 -1
  9. package/base64ToDataUrl.js +3 -1
  10. package/blobToDataUrl.js +10 -1
  11. package/checkPermission.js +8 -1
  12. package/copyToClipboard.js +37 -1
  13. package/createLoop.js +30 -1
  14. package/createRafLoop.js +56 -1
  15. package/dataUrlToBlob.js +13 -1
  16. package/fromBase64.js +10 -1
  17. package/fullscreen.js +167 -1
  18. package/fullscreenUtils.js +37 -1
  19. package/getAspectRatio.js +8 -1
  20. package/getBrowserLanguage.js +10 -1
  21. package/getCurrentScriptUrl.js +4 -1
  22. package/getEventAwaiter.js +41 -1
  23. package/getGeoCoordinates.js +6 -1
  24. package/getGeoLocality.js +19 -1
  25. package/getInnerRect.js +8 -1
  26. package/getInnerXDimensions.js +9 -1
  27. package/getInnerYDimensions.js +9 -1
  28. package/getPinchZoomHandlers.js +134 -1
  29. package/getRandomID.js +4 -1
  30. package/getScreenSize.js +23 -1
  31. package/getSecondsCounter.js +47 -1
  32. package/iframe/getAutoConnector.js +251 -1
  33. package/iframe/getOriginFromMessage.js +3 -1
  34. package/iframe/isIframeLoaded.js +9 -1
  35. package/iframe/messages.d.ts +2 -2
  36. package/iframe/messages.js +50 -1
  37. package/iframe/utils.js +33 -1
  38. package/imageToBlob.js +20 -1
  39. package/isImageTypeSupported.js +8 -1
  40. package/isWebPSupported.js +15 -1
  41. package/loadImage.js +29 -1
  42. package/loadScript.d.ts +1 -1
  43. package/loadScript.js +67 -1
  44. package/media/Capabilities.js +44 -1
  45. package/media/MediaNotAttachedError.d.ts +1 -1
  46. package/media/MediaNotAttachedError.js +6 -1
  47. package/media/MediaStreamController.js +84 -1
  48. package/media/PipController.d.ts +2 -2
  49. package/media/PipController.js +140 -1
  50. package/media/TextTracksController/TextTracksController.d.ts +0 -3
  51. package/media/TextTracksController/TextTracksController.js +251 -1
  52. package/media/TextTracksController/index.js +1 -1
  53. package/media/TextTracksController/utils.js +147 -1
  54. package/media/getDurationTime.js +3 -1
  55. package/media/getMediaSource.js +11 -1
  56. package/media/getSourceBuffer.js +5 -1
  57. package/media/isMediaSeekable.js +4 -1
  58. package/media/parseCueText.js +224 -1
  59. package/media/resetMedia.js +17 -1
  60. package/media/timeRanges.js +9 -1
  61. package/media/toggleNativeSubtitles.js +21 -1
  62. package/metrics/ga/DataLayerProxy.js +11 -1
  63. package/metrics/ga/getHandler.js +99 -1
  64. package/metrics/ga/types.js +1 -1
  65. package/metrics/types.js +1 -1
  66. package/metrics/yandex/DataLayerProxy.js +11 -1
  67. package/metrics/yandex/getHandler.js +63 -1
  68. package/metrics/yandex/types.js +2 -1
  69. package/onDOMReady.js +12 -1
  70. package/onPageReady.js +27 -1
  71. package/package.json +19 -16
  72. package/patchConsoleLogging.js +27 -1
  73. package/performance/getNavigationTiming.js +14 -1
  74. package/platform/Semver.js +23 -1
  75. package/platform/getChromeVersion.d.ts +2 -0
  76. package/platform/getChromeVersion.js +16 -0
  77. package/platform/getIOSVersion.js +18 -1
  78. package/platform/getPlatformInfo.js +49 -1
  79. package/platform/isAirPlayAvailable.js +3 -1
  80. package/platform/isAndroid.js +7 -1
  81. package/platform/isChrome.js +7 -1
  82. package/platform/isEMESupported.js +9 -1
  83. package/platform/isIOS.js +9 -1
  84. package/platform/isMSESupported.js +16 -1
  85. package/platform/isMacOS.js +12 -1
  86. package/platform/isMediaCapabilitiesSupported.js +6 -1
  87. package/platform/isMobile.js +16 -1
  88. package/platform/isMobileSimulation.js +7 -1
  89. package/platform/isSafari.js +14 -1
  90. package/platform/isScreenHDR.js +4 -1
  91. package/platform/isStandaloneApp.js +4 -1
  92. package/platform/isTelegramWebView.js +3 -1
  93. package/platform/isTouchSupported.js +4 -1
  94. package/preventDefault.d.ts +2 -2
  95. package/preventDefault.js +5 -1
  96. package/rafCallback.js +9 -1
  97. package/responsive/MediaQuery.d.ts +18 -0
  98. package/responsive/MediaQuery.js +40 -0
  99. package/responsive/MediaQueryListener.d.ts +19 -0
  100. package/responsive/MediaQueryListener.js +55 -0
  101. package/responsive/ViewSize.d.ts +45 -0
  102. package/responsive/ViewSize.js +82 -0
  103. package/responsive/getViewSizeQueryMap.d.ts +2 -0
  104. package/responsive/getViewSizeQueryMap.js +7 -0
  105. package/saveFileAs.js +22 -1
  106. package/serviceWorker/ServiceWorkerInstaller.d.ts +0 -1
  107. package/serviceWorker/ServiceWorkerInstaller.js +112 -1
  108. package/serviceWorker/utils.d.ts +1 -1
  109. package/serviceWorker/utils.js +86 -1
  110. package/stopPropagation.d.ts +2 -2
  111. package/stopPropagation.js +3 -1
  112. package/takeScreenshot.js +51 -1
  113. package/toBase64.js +9 -1
  114. package/toLocalPoint.js +9 -1
  115. package/types/index.js +2 -1
  116. package/types/refs.js +1 -1
  117. package/viewableTracker.js +69 -1
  118. package/webrtc/PeerConnection.js +212 -1
  119. package/webrtc/sdputils.js +417 -1
  120. package/ws/WSController.js +148 -1
@@ -1 +1,3 @@
1
- export function getOriginFromMessage({origin:n}){return n&&"null"!==n&&"undefined"!==n?n:"*"}
1
+ export function getOriginFromMessage({ origin }) {
2
+ return !origin || origin === 'null' || origin === 'undefined' ? '*' : origin;
3
+ }
@@ -1 +1,9 @@
1
- export function isIframeLoaded(e,{blank:t,complete:n}={}){if(!e.contentDocument)return!1;const{documentURI:o,readyState:c}=e.contentDocument;return(t||!!o&&"about:blank"!==o)&&(n?"complete"===c:"interactive"===c||"complete"===c)}
1
+ export function isIframeLoaded(iframe, { blank, complete } = {}) {
2
+ if (!iframe.contentDocument)
3
+ return false;
4
+ const { documentURI, readyState } = iframe.contentDocument;
5
+ return ((blank || (!!documentURI && documentURI !== 'about:blank')) &&
6
+ (complete
7
+ ? readyState === 'complete'
8
+ : readyState === 'interactive' || readyState === 'complete'));
9
+ }
@@ -1,11 +1,11 @@
1
1
  export declare const IFRAME_PING = "@_IFRAME_PING";
2
2
  export declare const IFRAME_HOST_READY = "@_IFRAME_HOST_READY";
3
3
  export declare const IFRAME_CLIENT_READY = "@_IFRAME_CLIENT_READY";
4
- export type MessagesTypes = {
4
+ export interface MessagesTypes {
5
5
  readonly Ping: string;
6
6
  readonly TargetReady: string;
7
7
  readonly SelfReady: string;
8
- };
8
+ }
9
9
  export declare function getHostMessages(): MessagesTypes;
10
10
  export declare function getClientMessages(): MessagesTypes;
11
11
  export interface IframeMessage<T extends keyof MessagesTypes = keyof MessagesTypes> {
@@ -1 +1,50 @@
1
- export const IFRAME_PING="@_IFRAME_PING";export const IFRAME_HOST_READY="@_IFRAME_HOST_READY";export const IFRAME_CLIENT_READY="@_IFRAME_CLIENT_READY";export function getHostMessages(){return{Ping:IFRAME_PING,SelfReady:IFRAME_HOST_READY,TargetReady:IFRAME_CLIENT_READY}}export function getClientMessages(){return{Ping:IFRAME_PING,SelfReady:IFRAME_CLIENT_READY,TargetReady:IFRAME_HOST_READY}}export function isPingMessage(e,t){return!!e&&e.type===t.Ping}export function isTargetReadyMessage(e,t){return!!e&&e.type===t.TargetReady}
1
+ export const IFRAME_PING = '@_IFRAME_PING';
2
+ export const IFRAME_HOST_READY = '@_IFRAME_HOST_READY';
3
+ export const IFRAME_CLIENT_READY = '@_IFRAME_CLIENT_READY';
4
+ export function getHostMessages() {
5
+ return {
6
+ Ping: IFRAME_PING,
7
+ SelfReady: IFRAME_HOST_READY,
8
+ TargetReady: IFRAME_CLIENT_READY,
9
+ };
10
+ }
11
+ export function getClientMessages() {
12
+ return {
13
+ Ping: IFRAME_PING,
14
+ SelfReady: IFRAME_CLIENT_READY,
15
+ TargetReady: IFRAME_HOST_READY,
16
+ };
17
+ }
18
+ // export type IframePingMessage = IframeMessage<'Ping'>;
19
+ // export interface IframeHostReadyMessage<T> extends IframeDataMessage<'HostReady', T> {}
20
+ // export interface IframeClientReadyMessage<T> extends IframeDataMessage<'ClientReady', T> {}
21
+ // export function isIframeMessage<T extends keyof MessagesTypes>(
22
+ // data: unknown,
23
+ // validType: T
24
+ // ): data is IframeMessage<T> {
25
+ // return !!data && (data as IframeMessage<T>).type === validType;
26
+ // }
27
+ // export function isIframeDataMessage<T extends string, D>(
28
+ // data: unknown,
29
+ // validType: T
30
+ // ): data is IframeDataMessage<T, D> {
31
+ // return !!data && (data as IframeDataMessage<T, D>).type === validType;
32
+ // }
33
+ export function isPingMessage(data, messagesTypes) {
34
+ return data != null && data.type === messagesTypes.Ping;
35
+ }
36
+ export function isTargetReadyMessage(data, messagesTypes) {
37
+ return data != null && data.type === messagesTypes.TargetReady;
38
+ }
39
+ // export function isHostReadyMessage<T>(
40
+ // data: unknown,
41
+ // messagesTypes: MessagesTypes
42
+ // ): data is IframeDataMessage<'HostReady', T> {
43
+ // return !!data && (data as IframeDataMessage).type === messagesTypes.HostReady;
44
+ // }
45
+ // export function isClientReadyMessage<T>(
46
+ // data: unknown,
47
+ // messagesTypes: MessagesTypes
48
+ // ): data is IframeDataMessage<'ClientReady', T> {
49
+ // return !!data && (data as IframeDataMessage).type === messagesTypes.ClientReady;
50
+ // }
package/iframe/utils.js CHANGED
@@ -1 +1,33 @@
1
- export function findTarget(e,n){const{length:t}=n;for(let o=0;o<t;o+=1){const t=n[o];if((t instanceof HTMLIFrameElement?t.contentWindow:t)===e)return t}}export function isWindowProxy(e){return!(void 0!==window.MessagePort&&e instanceof MessagePort||void 0!==window.ServiceWorker&&e instanceof ServiceWorker)}export function readTargets(e){const n=new Array,{length:t}=e;for(let o=0;o<t;o+=1){const t=e[o],r=t instanceof HTMLIFrameElement?t.contentWindow:t;r&&n.push(r)}return n}
1
+ export function findTarget(source, targets
2
+ // logger: Pick<Console, 'warn'> | undefined = console
3
+ ) {
4
+ const { length } = targets;
5
+ for (let i = 0; i < length; i += 1) {
6
+ const frame = targets[i];
7
+ const window = frame instanceof HTMLIFrameElement ? frame.contentWindow : frame;
8
+ if (window === source)
9
+ return frame;
10
+ // if (window == null) {
11
+ // logger.warn(`Search target: <iframe>(#${frame.id}) contentWindow is undefined.`);
12
+ // }
13
+ }
14
+ return undefined;
15
+ }
16
+ export function isWindowProxy(target) {
17
+ return !((window.MessagePort !== undefined && target instanceof MessagePort) ||
18
+ (window.ServiceWorker !== undefined && target instanceof ServiceWorker));
19
+ }
20
+ export function readTargets(list) {
21
+ // const result = new Array<Window>(list.length);
22
+ const result = new Array();
23
+ const { length } = list;
24
+ for (let i = 0; i < length; i += 1) {
25
+ const frame = list[i];
26
+ const window = frame instanceof HTMLIFrameElement ? frame.contentWindow : frame;
27
+ if (window) {
28
+ result.push(window);
29
+ // result[i] = window;
30
+ }
31
+ }
32
+ return result;
33
+ }
package/imageToBlob.js CHANGED
@@ -1 +1,20 @@
1
- import{get2dContextError}from"./takeScreenshot";export function imageToBlob(t){return new Promise((e,o)=>{const r=document.createElement("canvas");r.width=t.width,r.height=t.height;const n=r.getContext("2d");if(!n)throw get2dContextError();n.drawImage(t,0,0),r.toBlob(t=>{t?e(t):o(new Error("Unable to get blob from image."))})})}
1
+ import { get2dContextError } from './takeScreenshot';
2
+ export function imageToBlob(image) {
3
+ return new Promise((resolve, reject) => {
4
+ const canvas = document.createElement('canvas');
5
+ canvas.width = image.width;
6
+ canvas.height = image.height;
7
+ const ctx = canvas.getContext('2d');
8
+ if (!ctx)
9
+ throw get2dContextError();
10
+ ctx.drawImage(image, 0, 0);
11
+ canvas.toBlob((blob) => {
12
+ if (blob) {
13
+ resolve(blob);
14
+ }
15
+ else {
16
+ reject(new Error('Unable to get blob from image.'));
17
+ }
18
+ });
19
+ });
20
+ }
@@ -1 +1,8 @@
1
- export function isImageTypeSupported(t){try{return document.createElement("canvas").toDataURL(t).startsWith(`data:${t}`)}catch{return!1}}
1
+ export function isImageTypeSupported(mimeType) {
2
+ try {
3
+ return document.createElement('canvas').toDataURL(mimeType).startsWith(`data:${mimeType}`);
4
+ }
5
+ catch {
6
+ return false;
7
+ }
8
+ }
@@ -1 +1,15 @@
1
- export async function isWebPSupported(A="lossy"){const Q={lossy:"UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",lossless:"UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",alpha:"UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",animation:"UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"},o=new Image;return new Promise(l=>{o.onload=()=>l(o.width>0&&o.height>0),o.onerror=()=>l(!1),o.src=`data:image/webp;base64,${Q[A]}`})}
1
+ // https://developers.google.com/speed/webp/faq#how_can_i_detect_browser_support_for_webp
2
+ export async function isWebPSupported(feature = 'lossy') {
3
+ const testImages = {
4
+ lossy: 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA',
5
+ lossless: 'UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==',
6
+ alpha: 'UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==',
7
+ animation: 'UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA',
8
+ };
9
+ const img = new Image();
10
+ return new Promise((resolve) => {
11
+ img.onload = () => resolve(img.width > 0 && img.height > 0);
12
+ img.onerror = () => resolve(false);
13
+ img.src = `data:image/webp;base64,${testImages[feature]}`;
14
+ });
15
+ }
package/loadImage.js CHANGED
@@ -1 +1,29 @@
1
- export function loadImage(o){return new Promise((n,r)=>{const e=new Image;if(e.onload=()=>{e.onload=null,e.onerror=null,n(e)},e.onerror=o=>{e.onload=null,e.onerror=null,r(o)},"string"==typeof o)e.src=o;else{const{crossOrigin:n,...r}=o;void 0!==n&&(e.crossOrigin=n),Object.assign(e,r)}})}
1
+ /** Don't worry, it uses cached by browser image if url already loaded previously otherwise cache the image. */
2
+ export function loadImage(src
3
+ // crossOrigin?: HTMLImageElement['crossOrigin'] | undefined
4
+ ) {
5
+ return new Promise((resolve, reject) => {
6
+ const img = new Image();
7
+ img.onload = () => {
8
+ img.onload = null;
9
+ img.onerror = null;
10
+ resolve(img);
11
+ };
12
+ img.onerror = (event) => {
13
+ img.onload = null;
14
+ img.onerror = null;
15
+ // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
16
+ reject(event);
17
+ };
18
+ if (typeof src === 'string') {
19
+ img.src = src;
20
+ }
21
+ else {
22
+ const { crossOrigin, ...rest } = src;
23
+ if (crossOrigin !== undefined) {
24
+ img.crossOrigin = crossOrigin;
25
+ }
26
+ Object.assign(img, rest);
27
+ }
28
+ });
29
+ }
package/loadScript.d.ts CHANGED
@@ -8,4 +8,4 @@ export interface LoadScriptOptions extends Partial<Pick<HTMLScriptElement, 'id'>
8
8
  /** Defaults to `true`. */
9
9
  waitDomReady?: boolean | undefined;
10
10
  }
11
- export declare function loadScript(url: string, { id, keepScript, async, defer, waitDomReady, }?: LoadScriptOptions, isExecuted?: (() => boolean) | undefined): Promise<void>;
11
+ export declare function loadScript(url: string, { id, keepScript, async, defer, waitDomReady, }?: LoadScriptOptions, isExecuted?: () => boolean): Promise<void>;
package/loadScript.js CHANGED
@@ -1 +1,67 @@
1
- import{onDOMReady}from"./onDOMReady";function findScript(e){const r=e.startsWith("//")?window.location.protocol+e:e,{length:t}=document.scripts;for(let e=0;e<t;e+=1)if(document.scripts[e].src===r)return document.scripts[e]}function load(e,{id:r,keepScript:t,async:n,defer:o},c,d,i){try{const s=r?document.scripts.namedItem(r):findScript(e);if(s&&(!c||c()))return void d();const a=s??document.createElement("script"),p=()=>{a.removeEventListener("load",m),a.removeEventListener("error",f),t||s||a.remove()},m=()=>{p(),d()},f=r=>{p();const t=r instanceof ErrorEvent?r:new Error(`Unable to load script by url ${e}.`,{cause:r});i(t)};a.addEventListener("load",m,{once:!0}),a.addEventListener("error",f,{once:!0}),s||(r&&(a.id=r),a.async=n,a.defer=o,a.src=e,document.head.appendChild(a))}catch(e){i(e)}}export function loadScript(e,{id:r,keepScript:t=!1,async:n=!0,defer:o=!1,waitDomReady:c=!0}={},d=void 0){return new Promise((i,s)=>{c?onDOMReady(()=>{load(e,{id:r??"",keepScript:t,async:n,defer:o},d,i,s)}):load(e,{id:r??"",keepScript:t,async:n,defer:o},d,i,s)})}
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import { onDOMReady } from './onDOMReady';
3
+ function findScript(src) {
4
+ const url = src.startsWith('//') ? window.location.protocol + src : src;
5
+ const { length } = document.scripts;
6
+ for (let i = 0; i < length; i += 1) {
7
+ if (document.scripts[i].src === url) {
8
+ return document.scripts[i];
9
+ }
10
+ }
11
+ return undefined;
12
+ }
13
+ function load(url, { id, keepScript, async, defer }, isExecuted, resolve, reject) {
14
+ try {
15
+ const addedScript = id ? document.scripts.namedItem(id) : findScript(url);
16
+ if (addedScript && (!isExecuted || isExecuted())) {
17
+ resolve();
18
+ return;
19
+ }
20
+ const scriptElement = addedScript ?? document.createElement('script');
21
+ const done = () => {
22
+ scriptElement.removeEventListener('load', onLoad);
23
+ scriptElement.removeEventListener('error', onError);
24
+ if (!keepScript && !addedScript) {
25
+ scriptElement.remove();
26
+ }
27
+ };
28
+ const onLoad = () => {
29
+ done();
30
+ resolve();
31
+ };
32
+ const onError = (error) => {
33
+ done();
34
+ const ex = error instanceof ErrorEvent
35
+ ? error
36
+ : new Error(`Unable to load script by url ${url}.`, { cause: error });
37
+ reject(ex);
38
+ };
39
+ // Subscribe in any way because the script may be already added but not executed/loaded yet.
40
+ scriptElement.addEventListener('load', onLoad, { once: true });
41
+ scriptElement.addEventListener('error', onError, { once: true });
42
+ if (!addedScript) {
43
+ if (id) {
44
+ scriptElement.id = id;
45
+ }
46
+ scriptElement.async = async;
47
+ scriptElement.defer = defer;
48
+ scriptElement.src = url;
49
+ document.head.appendChild(scriptElement);
50
+ }
51
+ }
52
+ catch (error) {
53
+ reject(error);
54
+ }
55
+ }
56
+ export function loadScript(url, { id, keepScript = false, async = true, defer = false, waitDomReady = true, } = {}, isExecuted) {
57
+ return new Promise((resolve, reject) => {
58
+ if (waitDomReady) {
59
+ onDOMReady(() => {
60
+ load(url, { id: id ?? '', keepScript, async, defer }, isExecuted, resolve, reject);
61
+ });
62
+ }
63
+ else {
64
+ load(url, { id: id ?? '', keepScript, async, defer }, isExecuted, resolve, reject);
65
+ }
66
+ });
67
+ }
@@ -1 +1,44 @@
1
- import{getMediaSource}from"./getMediaSource";export class Capabilities{static supportMap=new Map;static canPlayMap=new Map;static tmpVideo;static cacheTimer;static isCanPlayType(t){if(this.canPlayMap.has(t))return!!this.canPlayMap.get(t);this.tmpVideo||(this.tmpVideo=document.getElementsByTagName("video")[0]||document.createElement("video"),window.clearTimeout(this.cacheTimer),this.cacheTimer=window.setTimeout(()=>{this.tmpVideo=void 0},1e3));const e=!!this.tmpVideo.canPlayType(t);return this.supportMap.set(t,e),e}static isTypeSupported(t,e){if(this.supportMap.has(t))return!!this.supportMap.get(t);const i=getMediaSource(e);if(i){const e=i.isTypeSupported(t);return this.supportMap.set(t,e),e}return!1}static reset(){window.clearTimeout(this.cacheTimer),this.tmpVideo=void 0,this.supportMap.clear(),this.canPlayMap.clear()}}
1
+ /* eslint-disable @typescript-eslint/no-extraneous-class */
2
+ import { getMediaSource } from './getMediaSource';
3
+ export class Capabilities {
4
+ static supportMap = new Map();
5
+ static canPlayMap = new Map();
6
+ static tmpVideo;
7
+ static cacheTimer;
8
+ /** Check video element. Mime type and optional codecs. */
9
+ static isCanPlayType(type) {
10
+ if (this.canPlayMap.has(type)) {
11
+ return !!this.canPlayMap.get(type);
12
+ }
13
+ if (!this.tmpVideo) {
14
+ this.tmpVideo = document.getElementsByTagName('video')[0] ?? document.createElement('video');
15
+ // Release handle for GC work.
16
+ window.clearTimeout(this.cacheTimer);
17
+ this.cacheTimer = window.setTimeout(() => {
18
+ this.tmpVideo = undefined;
19
+ }, 1000);
20
+ }
21
+ const support = !!this.tmpVideo.canPlayType(type);
22
+ this.supportMap.set(type, support);
23
+ return support;
24
+ }
25
+ /** Check MediaSource. */
26
+ static isTypeSupported(type, managedMediaSource) {
27
+ if (this.supportMap.has(type)) {
28
+ return !!this.supportMap.get(type);
29
+ }
30
+ const mediaSource = getMediaSource(managedMediaSource);
31
+ if (mediaSource) {
32
+ const support = mediaSource.isTypeSupported(type);
33
+ this.supportMap.set(type, support);
34
+ return support;
35
+ }
36
+ return false;
37
+ }
38
+ static reset() {
39
+ window.clearTimeout(this.cacheTimer);
40
+ this.tmpVideo = undefined;
41
+ this.supportMap.clear();
42
+ this.canPlayMap.clear();
43
+ }
44
+ }
@@ -1,4 +1,4 @@
1
1
  import { ErrorCompat } from '@js-toolkit/utils/ErrorCompat';
2
2
  export declare class MediaNotAttachedError extends ErrorCompat {
3
- constructor(message?: string, options?: ErrorOptions | undefined);
3
+ constructor(message?: string, options?: ErrorOptions);
4
4
  }
@@ -1 +1,6 @@
1
- import{ErrorCompat}from"@js-toolkit/utils/ErrorCompat";export class MediaNotAttachedError extends ErrorCompat{constructor(t="Media element is not attached yet.",r=void 0){super(MediaNotAttachedError,t,{...r,name:"MediaNotAttachedError "})}}
1
+ import { ErrorCompat } from '@js-toolkit/utils/ErrorCompat';
2
+ export class MediaNotAttachedError extends ErrorCompat {
3
+ constructor(message = 'Media element is not attached yet.', options) {
4
+ super(MediaNotAttachedError, message, { ...options, name: 'MediaNotAttachedError ' });
5
+ }
6
+ }
@@ -1 +1,84 @@
1
- import{resetMedia}from"./resetMedia";export function attachMediaStream(e,t){t&&t.active&&e.muted&&0===t.getVideoTracks().filter(e=>!e.getSettings().displaySurface).length?e.srcObject=null:e.srcObject=t&&t.active?t:null}export function removeTrack(e,t){e.removeTrack(t),e.dispatchEvent(new MediaStreamTrackEvent("removetrack",{track:t}))}export function addTrack(e,t,a){e.addTrack(t),e.dispatchEvent(new MediaStreamTrackEvent("addtrack",{track:t})),t.addEventListener("ended",()=>{removeTrack(e,t),a&&a()},{once:!0})}export class MediaStreamController{mediaStream;media;constructor(e){this.mediaStream=e}attach(e){this.media=e,attachMediaStream(e,this.mediaStream)}detach(){if(this.media){const{media:e}=this;this.media=void 0,resetMedia(e)}}updateStream(e){if(this.mediaStream===e)return;const{media:t}=this;this.mediaStream=e,t&&this.attach(t)}removeTrack(e){removeTrack(this.mediaStream,e)}addTrack(e,t){addTrack(this.mediaStream,e,()=>{this.removeTrack(e),t&&t()})}reset(){this.mediaStream.getTracks().forEach(e=>{e.stop(),this.removeTrack(e)})}destroy(){this.detach(),this.reset()}[Symbol.dispose](){this.destroy()}}
1
+ import { resetMedia } from './resetMedia';
2
+ export function attachMediaStream(media, stream) {
3
+ // Reassign stream will trigger events onEmptied, onLoadStart and so on.
4
+ // If stream is empty or has only audio and muted then will fire onLoadStart and stop on it,
5
+ // so we need assign null instead of empty stream in order for fire onEmptied and stop on it.
6
+ if (stream &&
7
+ stream.active &&
8
+ media.muted &&
9
+ // Assign null if screen sharing (especially for local stream)
10
+ stream.getVideoTracks().filter((t) => !t.getSettings().displaySurface).length === 0) {
11
+ media.srcObject = null;
12
+ return;
13
+ }
14
+ media.srcObject = stream?.active ? stream : null;
15
+ }
16
+ export function removeTrack(mediaStream, track) {
17
+ mediaStream.removeTrack(track);
18
+ // Dispatch event manually because on local stream it not fired
19
+ mediaStream.dispatchEvent(new MediaStreamTrackEvent('removetrack', { track }));
20
+ }
21
+ export function addTrack(mediaStream, track, onEnded) {
22
+ mediaStream.addTrack(track);
23
+ // Dispatch event manually because on local stream it not fired
24
+ mediaStream.dispatchEvent(new MediaStreamTrackEvent('addtrack', { track }));
25
+ // When track is uncontrolled ended then remove it from stream
26
+ track.addEventListener('ended', () => {
27
+ removeTrack(mediaStream, track);
28
+ if (onEnded)
29
+ onEnded();
30
+ }, { once: true });
31
+ }
32
+ export class MediaStreamController {
33
+ mediaStream;
34
+ media;
35
+ constructor(mediaStream) {
36
+ this.mediaStream = mediaStream;
37
+ }
38
+ attach(media) {
39
+ // if (this.media !== media) {
40
+ // this.detachMedia();
41
+ // }
42
+ this.media = media;
43
+ attachMediaStream(media, this.mediaStream);
44
+ }
45
+ detach() {
46
+ if (this.media) {
47
+ const { media } = this;
48
+ this.media = undefined;
49
+ resetMedia(media);
50
+ }
51
+ }
52
+ updateStream(stream) {
53
+ if (this.mediaStream === stream)
54
+ return;
55
+ const { media } = this;
56
+ // this.detachMedia();
57
+ this.mediaStream = stream;
58
+ if (media)
59
+ this.attach(media);
60
+ }
61
+ removeTrack(track) {
62
+ removeTrack(this.mediaStream, track);
63
+ }
64
+ addTrack(track, onEnded) {
65
+ addTrack(this.mediaStream, track, () => {
66
+ this.removeTrack(track);
67
+ if (onEnded)
68
+ onEnded();
69
+ });
70
+ }
71
+ reset() {
72
+ this.mediaStream.getTracks().forEach((track) => {
73
+ track.stop();
74
+ this.removeTrack(track);
75
+ });
76
+ }
77
+ destroy() {
78
+ this.detach();
79
+ this.reset();
80
+ }
81
+ [Symbol.dispose]() {
82
+ this.destroy();
83
+ }
84
+ }
@@ -29,9 +29,9 @@ export declare namespace PipController {
29
29
  enum Events {
30
30
  Change = "change"
31
31
  }
32
- type EventMap = {
32
+ type EventMap = EventEmitter.EventMap<DefineAll<Events, {
33
33
  [Events.Change]: [{
34
34
  pip: boolean;
35
35
  }];
36
- };
36
+ }>, PipController>;
37
37
  }
@@ -1 +1,140 @@
1
- import{EventEmitter}from"@js-toolkit/utils/EventEmitter";import{EventEmitterListener}from"../EventEmitterListener";import{toggleNativeSubtitles}from"./toggleNativeSubtitles";const getPipUnavailableError=()=>new Error("PiP is not available");export class PipController extends EventEmitter{options;static isApiEnabled(){return!!HTMLVideoElement.prototype.requestPictureInPicture&&!!document.exitPictureInPicture&&!!document.pictureInPictureEnabled}static isWebkitApiEnabled(e){return!!e.webkitSupportsPresentationMode&&e.webkitSupportsPresentationMode("picture-in-picture")}static isAvailable(e){return this.isApiEnabled()&&!e.disablePictureInPicture||this.isWebkitApiEnabled(e)}get Events(){return PipController.Events}listener;constructor(e,t={}){if(super(),this.options=t,this.listener=new EventEmitterListener(e),PipController.isAvailable(e)){const e=(()=>{const e=()=>{e.nativeSubtitles=this.options.toggleNativeSubtitles&&this.listener.target.textTracks.length>0,e.nativeSubtitles&&toggleNativeSubtitles(!0,this.listener.target.textTracks),this.emit(this.Events.Change,{pip:!0})};return e.nativeSubtitles=void 0,e})(),t=()=>{e.nativeSubtitles&&toggleNativeSubtitles(!1,this.listener.target.textTracks),this.emit(this.Events.Change,{pip:!1})};PipController.isApiEnabled()?(this.listener.on("enterpictureinpicture",e),this.listener.on("leavepictureinpicture",t)):this.listener.on("webkitpresentationmodechanged",(()=>{let i=this.listener.target.webkitPresentationMode;return()=>{"picture-in-picture"===this.listener.target.webkitPresentationMode?e():"picture-in-picture"===i&&t(),i=this.listener.target.webkitPresentationMode}})(),!0)}}isPip(){return PipController.isApiEnabled()?document.pictureInPictureElement===this.listener.target:"picture-in-picture"===this.listener.target.webkitPresentationMode}getCurrentElement(){return this.isPip()?this.listener.target:null}request(){return new Promise((e,t)=>{if(this.isPip())e();else{if(!PipController.isAvailable(this.listener.target))throw getPipUnavailableError();PipController.isApiEnabled()?this.listener.target.requestPictureInPicture().then(()=>e(),t):(this.listener.once("webkitpresentationmodechanged",()=>{"picture-in-picture"===this.listener.target.webkitPresentationMode?e():t(new Error("Something went wrong."))},!0),this.listener.target.webkitSetPresentationMode("picture-in-picture"))}})}exit(){return new Promise((e,t)=>{if(this.isPip()){if(!PipController.isAvailable(this.listener.target))throw getPipUnavailableError();PipController.isApiEnabled()?document.exitPictureInPicture().then(e,t):(this.listener.once("webkitpresentationmodechanged",()=>{"picture-in-picture"!==this.listener.target.webkitPresentationMode?e():t(new Error("Something went wrong."))},!0),this.listener.target.webkitSetPresentationMode("inline"))}else e()})}destroy(){return this.exit().finally(()=>{this.removeAllListeners(),this.listener.removeAllListeners()})}[Symbol.asyncDispose](){return this.destroy()}}!function(e){let t;!function(e){e.Change="change"}(t=e.Events||(e.Events={}))}(PipController||(PipController={}));
1
+ /* eslint-disable @typescript-eslint/no-unnecessary-type-conversion */
2
+ /* eslint-disable @typescript-eslint/strict-boolean-expressions */
3
+ import { EventEmitter } from '@js-toolkit/utils/EventEmitter';
4
+ import { EventEmitterListener } from '../EventEmitterListener';
5
+ import { toggleNativeSubtitles } from './toggleNativeSubtitles';
6
+ const getPipUnavailableError = () => new Error('PiP is not available');
7
+ export class PipController extends EventEmitter {
8
+ options;
9
+ static isApiEnabled() {
10
+ return (!!HTMLVideoElement.prototype.requestPictureInPicture &&
11
+ !!document.exitPictureInPicture &&
12
+ !!document.pictureInPictureEnabled);
13
+ }
14
+ // Only able to call on instance
15
+ static isWebkitApiEnabled(media) {
16
+ return (!!media.webkitSupportsPresentationMode &&
17
+ media.webkitSupportsPresentationMode('picture-in-picture'));
18
+ }
19
+ static isAvailable(video) {
20
+ return ((this.isApiEnabled() && !video.disablePictureInPicture) || this.isWebkitApiEnabled(video));
21
+ }
22
+ // eslint-disable-next-line class-methods-use-this
23
+ get Events() {
24
+ return PipController.Events;
25
+ }
26
+ listener;
27
+ constructor(video, options = {}) {
28
+ super();
29
+ this.options = options;
30
+ this.listener = new EventEmitterListener(video);
31
+ if (PipController.isAvailable(video)) {
32
+ const enterPipHandler = (() => {
33
+ const handler = () => {
34
+ handler.nativeSubtitles =
35
+ this.options.toggleNativeSubtitles && this.listener.target.textTracks.length > 0;
36
+ if (handler.nativeSubtitles) {
37
+ toggleNativeSubtitles(true, this.listener.target.textTracks);
38
+ }
39
+ this.emit(this.Events.Change, { pip: true });
40
+ };
41
+ handler.nativeSubtitles = undefined;
42
+ return handler;
43
+ })();
44
+ const exitPipHandler = () => {
45
+ if (enterPipHandler.nativeSubtitles) {
46
+ toggleNativeSubtitles(false, this.listener.target.textTracks);
47
+ }
48
+ this.emit(this.Events.Change, { pip: false });
49
+ };
50
+ if (PipController.isApiEnabled()) {
51
+ this.listener.on('enterpictureinpicture', enterPipHandler);
52
+ this.listener.on('leavepictureinpicture', exitPipHandler);
53
+ }
54
+ else {
55
+ this.listener.on('webkitpresentationmodechanged', (() => {
56
+ let lastMode = this.listener.target.webkitPresentationMode;
57
+ return () => {
58
+ if (this.listener.target.webkitPresentationMode === 'picture-in-picture') {
59
+ enterPipHandler();
60
+ }
61
+ else if (lastMode === 'picture-in-picture') {
62
+ exitPipHandler();
63
+ }
64
+ lastMode = this.listener.target.webkitPresentationMode;
65
+ };
66
+ })(), true);
67
+ }
68
+ }
69
+ }
70
+ isPip() {
71
+ return PipController.isApiEnabled()
72
+ ? document.pictureInPictureElement === this.listener.target
73
+ : this.listener.target.webkitPresentationMode === 'picture-in-picture';
74
+ }
75
+ getCurrentElement() {
76
+ return this.isPip() ? this.listener.target : null;
77
+ }
78
+ request() {
79
+ return new Promise((resolve, reject) => {
80
+ if (this.isPip()) {
81
+ resolve();
82
+ return;
83
+ }
84
+ if (!PipController.isAvailable(this.listener.target)) {
85
+ throw getPipUnavailableError();
86
+ }
87
+ if (PipController.isApiEnabled()) {
88
+ void this.listener.target.requestPictureInPicture().then(() => resolve(), reject);
89
+ }
90
+ else {
91
+ this.listener.once('webkitpresentationmodechanged', () => {
92
+ if (this.listener.target.webkitPresentationMode === 'picture-in-picture')
93
+ resolve();
94
+ else
95
+ reject(new Error('Something went wrong.'));
96
+ }, true);
97
+ this.listener.target.webkitSetPresentationMode('picture-in-picture');
98
+ }
99
+ });
100
+ }
101
+ exit() {
102
+ return new Promise((resolve, reject) => {
103
+ if (!this.isPip()) {
104
+ resolve();
105
+ return;
106
+ }
107
+ if (!PipController.isAvailable(this.listener.target)) {
108
+ throw getPipUnavailableError();
109
+ }
110
+ if (PipController.isApiEnabled()) {
111
+ void document.exitPictureInPicture().then(resolve, reject);
112
+ }
113
+ else {
114
+ this.listener.once('webkitpresentationmodechanged', () => {
115
+ if (this.listener.target.webkitPresentationMode !== 'picture-in-picture')
116
+ resolve();
117
+ else
118
+ reject(new Error('Something went wrong.'));
119
+ }, true);
120
+ this.listener.target.webkitSetPresentationMode('inline');
121
+ }
122
+ });
123
+ }
124
+ destroy() {
125
+ return this.exit().finally(() => {
126
+ this.removeAllListeners();
127
+ this.listener.removeAllListeners();
128
+ });
129
+ }
130
+ [Symbol.asyncDispose]() {
131
+ return this.destroy();
132
+ }
133
+ }
134
+ // eslint-disable-next-line @typescript-eslint/no-namespace
135
+ (function (PipController) {
136
+ let Events;
137
+ (function (Events) {
138
+ Events["Change"] = "change";
139
+ })(Events = PipController.Events || (PipController.Events = {}));
140
+ })(PipController || (PipController = {}));
@@ -6,9 +6,6 @@ declare global {
6
6
  interface TextTrack {
7
7
  native?: boolean | undefined;
8
8
  }
9
- interface TextTrackList {
10
- [index: number]: TextTrack | undefined;
11
- }
12
9
  }
13
10
  interface TextTracksEventMap {
14
11
  texttracklistchange: CustomEvent<TextTracksController.EventMap[TextTracksController.Events.TextTrackListChanged][0]>;