|
1 |
| -import Hls, { |
2 |
| - Loader, |
3 |
| - LoaderCallbacks, |
4 |
| - LoaderConfiguration, |
5 |
| - LoaderContext, |
6 |
| - LoaderResponse, |
7 |
| - LoaderStats, |
8 |
| - HlsConfig |
9 |
| -} from 'hls.js'; |
| 1 | +import Hls, { Loader, LoaderCallbacks, LoaderConfiguration, LoaderContext, HlsConfig } from 'hls.js'; |
10 | 2 | import { useEffect, useRef } from 'react';
|
11 | 3 |
|
12 | 4 | class CustomLoader implements Loader<LoaderContext> {
|
13 | 5 | private loader: Loader<LoaderContext>;
|
| 6 | + private retryCount: number = 0; |
| 7 | + private maxRetries: number = 6; |
| 8 | + private retryDelay: number = 3000; |
| 9 | + private DefaultLoader: new (config: HlsConfig) => Loader<LoaderContext>; |
14 | 10 |
|
15 | 11 | constructor(config: HlsConfig) {
|
16 |
| - const DefaultLoader = Hls.DefaultConfig.loader as new (config: HlsConfig) => Loader<LoaderContext>; |
17 |
| - this.loader = new DefaultLoader(config); |
| 12 | + this.DefaultLoader = Hls.DefaultConfig.loader as new (config: HlsConfig) => Loader<LoaderContext>; |
| 13 | + this.loader = new this.DefaultLoader(config); |
18 | 14 | }
|
19 | 15 |
|
20 |
| - load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>) { |
21 |
| - const { onSuccess, onError, onTimeout, onAbort, onProgress } = callbacks; |
| 16 | + private createNewLoader(): Loader<LoaderContext> { |
| 17 | + return new this.DefaultLoader({ |
| 18 | + ...Hls.DefaultConfig, |
| 19 | + enableWorker: true, |
| 20 | + lowLatencyMode: true |
| 21 | + }); |
| 22 | + } |
22 | 23 |
|
23 |
| - const newCallbacks: LoaderCallbacks<LoaderContext> = { |
24 |
| - onSuccess: (response: LoaderResponse, stats: LoaderStats, context: LoaderContext, networkDetails: any = null) => { |
25 |
| - onSuccess(response, stats, context, networkDetails); |
26 |
| - }, |
27 |
| - onError: ( |
28 |
| - error: { code: number; text: string }, |
29 |
| - context: LoaderContext, |
30 |
| - networkDetails: any = null, |
31 |
| - stats: LoaderStats |
32 |
| - ) => { |
33 |
| - if (error.code === 404) { |
34 |
| - const emptyData = new ArrayBuffer(0); |
35 |
| - onSuccess( |
36 |
| - { |
37 |
| - url: context.url, |
38 |
| - data: emptyData |
39 |
| - }, |
40 |
| - { |
41 |
| - trequest: performance.now(), |
42 |
| - tfirst: performance.now(), |
43 |
| - tload: performance.now(), |
44 |
| - loaded: 0, |
45 |
| - total: 0 |
46 |
| - } as unknown as LoaderStats, |
47 |
| - context, |
48 |
| - networkDetails |
49 |
| - ); |
50 |
| - } else { |
51 |
| - if (onError) { |
52 |
| - onError(error, context, networkDetails, stats); |
| 24 | + private retryLoad(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>) { |
| 25 | + this.loader = this.createNewLoader(); |
| 26 | + |
| 27 | + setTimeout(() => { |
| 28 | + const retryCallbacks: LoaderCallbacks<LoaderContext> = { |
| 29 | + ...callbacks, |
| 30 | + onError: (error, context, networkDetails, stats) => { |
| 31 | + if (error.code === 404 && this.retryCount < this.maxRetries) { |
| 32 | + this.retryCount++; |
| 33 | + this.retryLoad(context, config, callbacks); |
| 34 | + } else { |
| 35 | + this.retryCount = 0; |
| 36 | + if (callbacks.onError) { |
| 37 | + callbacks.onError(error, context, networkDetails, stats); |
| 38 | + } |
53 | 39 | }
|
54 | 40 | }
|
55 |
| - }, |
56 |
| - onTimeout: (stats: LoaderStats, context: LoaderContext, networkDetails: any = null) => { |
57 |
| - if (onTimeout) { |
58 |
| - onTimeout(stats, context, networkDetails); |
59 |
| - } |
60 |
| - }, |
61 |
| - onAbort: (stats: LoaderStats, context: LoaderContext, networkDetails: any = null) => { |
62 |
| - if (onAbort) { |
63 |
| - onAbort(stats, context, networkDetails); |
| 41 | + }; |
| 42 | + |
| 43 | + this.loader.load(context, config, retryCallbacks); |
| 44 | + }, this.retryDelay); |
| 45 | + } |
| 46 | + |
| 47 | + load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>) { |
| 48 | + const modifiedCallbacks: LoaderCallbacks<LoaderContext> = { |
| 49 | + ...callbacks, |
| 50 | + onSuccess: (response, stats, context, networkDetails) => { |
| 51 | + this.retryCount = 0; |
| 52 | + if (callbacks.onSuccess) { |
| 53 | + callbacks.onSuccess(response, stats, context, networkDetails); |
64 | 54 | }
|
65 | 55 | },
|
66 |
| - onProgress: ( |
67 |
| - stats: LoaderStats, |
68 |
| - context: LoaderContext, |
69 |
| - data: string | ArrayBuffer, |
70 |
| - networkDetails: any = null |
71 |
| - ) => { |
72 |
| - if (onProgress) { |
73 |
| - onProgress(stats, context, data, networkDetails); |
| 56 | + onError: (error, context, networkDetails, stats) => { |
| 57 | + if (error.code === 404 && this.retryCount < this.maxRetries) { |
| 58 | + this.retryCount++; |
| 59 | + this.retryLoad(context, config, callbacks); |
| 60 | + } else { |
| 61 | + this.retryCount = 0; |
| 62 | + if (callbacks.onError) { |
| 63 | + callbacks.onError(error, context, networkDetails, stats); |
| 64 | + } |
74 | 65 | }
|
75 | 66 | }
|
76 | 67 | };
|
77 | 68 |
|
78 |
| - this.loader.load(context, config, newCallbacks); |
| 69 | + this.loader.load(context, config, modifiedCallbacks); |
79 | 70 | }
|
80 | 71 |
|
81 | 72 | abort() {
|
@@ -124,6 +115,22 @@ export default function usePlayer(url: string) {
|
124 | 115 | videoElement.play();
|
125 | 116 | });
|
126 | 117 |
|
| 118 | + hls.on(Hls.Events.ERROR, (event, data) => { |
| 119 | + if (data.fatal) { |
| 120 | + switch (data.type) { |
| 121 | + case Hls.ErrorTypes.NETWORK_ERROR: |
| 122 | + hls.startLoad(); |
| 123 | + break; |
| 124 | + case Hls.ErrorTypes.MEDIA_ERROR: |
| 125 | + hls.recoverMediaError(); |
| 126 | + break; |
| 127 | + default: |
| 128 | + hls.destroy(); |
| 129 | + break; |
| 130 | + } |
| 131 | + } |
| 132 | + }); |
| 133 | + |
127 | 134 | return () => {
|
128 | 135 | hls.destroy();
|
129 | 136 | };
|
|
0 commit comments