-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstream.js
More file actions
187 lines (168 loc) · 6.47 KB
/
Copy pathstream.js
File metadata and controls
187 lines (168 loc) · 6.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/**
* HLS Stream Handler
* Manages video playback and HLS events
*/
const Stream = {
hls: null,
dash: null,
videoElement: null,
currentUrl: null,
isRetry: false,
init(videoElement, onError) {
this.videoElement = videoElement;
this.onError = onError;
},
load(url, isRetry = false) {
this.destroy(); // Clean up previous players
this.currentUrl = url;
this.isRetry = isRetry;
const lowerUrl = url.toLowerCase();
if (typeof Controls !== 'undefined' && Controls.updateAudioTracks) {
Controls.updateAudioTracks([]); // Hide audio tracks initially
}
// 1. DASH (.mpd)
if (lowerUrl.includes('.mpd') && window.dashjs) {
console.log('Detected DASH stream');
this.dash = dashjs.MediaPlayer().create();
this.dash.initialize(this.videoElement, url, true);
this.dash.on(dashjs.MediaPlayer.events.ERROR, (e) => {
console.error('DASH Error:', e);
if (!this.isRetry) {
console.log('Retrying DASH via safe proxy...');
const proxyUrl = `${window.location.origin}/proxy?url=${encodeURIComponent(url)}`;
this.load(proxyUrl, true);
} else {
if (this.onError) this.onError(e);
}
});
return;
}
// 2. HLS (.m3u8) via HLS.js
if (window.Hls && Hls.isSupported()) {
// Fetch dynamic buffer level from Storage
const bufferSecs = typeof Storage !== 'undefined' ? parseInt(Storage.get('iptv_buffer_level', '60')) : 60;
this.hls = new Hls({
// --- Buffer Settings ---
maxBufferLength: bufferSecs, // dynamically configured buffer
maxMaxBufferLength: bufferSecs * 2,
maxBufferSize: bufferSecs * 1000 * 1000,
maxBufferHole: 2, // tolerate up to 2s gaps
highBufferWatchdogPeriod: 4, // check buffer health every 4s
// --- Latency ---
enableWorker: true,
lowLatencyMode: false, // disable LLM for stability
liveSyncDurationCount: 4, // sync point
liveMaxLatencyDurationCount: 10,
liveDurationInfinity: true,
// --- Retry & Recovery ---
fragLoadingMaxRetry: 8,
manifestLoadingMaxRetry: 5,
levelLoadingMaxRetry: 5,
fragLoadingRetryDelay: 1000, // 1s between retries
fragLoadingMaxRetryTimeout: 64000,
// --- Adaptive bitrate ---
startLevel: -1, // auto pick best quality level
abrEwmaDefaultEstimate: 5e6, // assume 5 Mbps initially
abrBandWidthFactor: 0.9,
abrBandWidthUpFactor: 0.7,
});
this.hls.loadSource(url);
this.hls.attachMedia(this.videoElement);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.videoElement.play().catch(e => console.log('Auto-play prevented:', e));
});
this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (event, data) => {
const tracks = this.hls.audioTracks;
if (typeof Controls !== 'undefined' && Controls.updateAudioTracks) {
Controls.updateAudioTracks(tracks);
}
});
this.hls.on(Hls.Events.ERROR, (event, data) => {
if (data.fatal) {
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
console.log('fatal network error encountered, try to recover');
if (!this.isRetry) {
console.log('CORS/Network error. Retrying via safe local proxy...');
const proxyUrl = `${window.location.origin}/proxy?url=${encodeURIComponent(url)}`;
this.load(proxyUrl, true);
} else {
this.hls.startLoad();
}
break;
case Hls.ErrorTypes.MEDIA_ERROR:
console.log('fatal media error encountered, try to recover');
this.hls.recoverMediaError();
break;
default:
this.hls.destroy();
let cause = 'network';
const errDetails = data.details || '';
if (errDetails.includes('keySystem') || errDetails.includes('fragDecry')) {
cause = 'drm';
} else if (data.response && (data.response.code === 403 || data.response.code === 0)) {
cause = 'cors'; // 0 usually means CORS block
} else if (data.response && data.response.code === 404) {
cause = 'notfound';
}
if (!this.isRetry && (cause === 'cors' || cause === 'network')) {
console.log('CORS/Network error. Retrying via safe local proxy...');
const proxyUrl = `${window.location.origin}/proxy?url=${encodeURIComponent(url)}`;
this.load(proxyUrl, true);
} else {
if (this.onError) this.onError({ cause: cause, details: data });
}
break;
}
}
});
return;
}
// 3. Native HLS (Safari) or Direct File (.mp4, .mkv, etc.)
if (this.videoElement.canPlayType('application/vnd.apple.mpegurl') ||
lowerUrl.includes('.mp4') ||
lowerUrl.includes('.mkv')) {
this.videoElement.src = url;
this.videoElement.addEventListener('loadedmetadata', () => {
this.videoElement.play().catch(e => console.log('Auto-play prevented:', e));
});
this.videoElement.onerror = (e) => {
if (!this.isRetry) {
console.log('Native playback error. Retrying via safe local proxy...');
const proxyUrl = `${window.location.origin}/proxy?url=${encodeURIComponent(url)}`;
this.load(proxyUrl, true);
} else {
if (this.onError) this.onError({ cause: 'unsupported', details: e });
}
};
return;
}
// Fallback: Try native anyway
this.videoElement.src = url;
this.videoElement.play().catch(e => {
if (!this.isRetry) {
console.log('Fallback player error. Retrying via safe local proxy...');
const proxyUrl = `${window.location.origin}/proxy?url=${encodeURIComponent(url)}`;
this.load(proxyUrl, true);
} else {
if (this.onError) this.onError({ cause: 'unsupported', details: e });
}
});
},
destroy() {
if (this.hls) {
this.hls.destroy();
this.hls = null;
}
if (this.dash) {
this.dash.reset();
this.dash = null;
}
// Stop native playback
if (this.videoElement) {
this.videoElement.pause();
this.videoElement.removeAttribute('src');
this.videoElement.load();
}
}
};