myetv-player 1.7.0 → 1.7.2
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.
- package/README.md +461 -154
- package/css/myetv-player.css +36 -0
- package/css/myetv-player.min.css +1 -1
- package/dist/myetv-player.js +415 -89
- package/dist/myetv-player.min.js +338 -60
- package/package.json +2 -1
- package/plugins/autosub/myetv-autosub-plugin.js +545 -188
- package/plugins/autosub/readme.md +156 -66
- package/plugins/radio/api.php +154 -0
- package/plugins/radio/icons/bolt-lightning-solid.png +0 -0
- package/plugins/radio/icons/compact-disc-solid.png +0 -0
- package/plugins/radio/icons/guitar-solid.png +0 -0
- package/plugins/radio/icons/headphones-solid.png +0 -0
- package/plugins/radio/icons/medal-solid.png +0 -0
- package/plugins/radio/icons/music-solid.png +0 -0
- package/plugins/radio/icons/newspaper-solid.png +0 -0
- package/plugins/radio/icons/radio-solid.png +0 -0
- package/plugins/radio/icons/readme.md +1 -0
- package/plugins/radio/myetv-radio-plugin.js +1655 -0
- package/plugins/radio/php/data/readme.md +1 -0
- package/plugins/radio/php/radio_stats.php +203 -0
- package/plugins/radio/proxy.php +26 -0
- package/plugins/radio/readme.md +176 -0
- package/plugins/radio/station_example.json +47 -0
- package/plugins/video-loading/myetv-video-loading-plugin.js +157 -0
- package/plugins/video-loading/readme.md +87 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
# myetv-autosub-plugin
|
|
2
2
|
|
|
3
|
-
> 🎙️ Auto-subtitles plugin for **myetv-player** — client-side speech recognition via [Whisper AI](https://huggingface.co/Xenova/whisper-tiny) + [Transformers.js](https://github.com/huggingface/transformers.js), no server, no FFmpeg, no API key required.
|
|
3
|
+
> 🎙️ Auto-subtitles plugin for **myetv-player** — client-side speech recognition via [Whisper AI](https://huggingface.co/Xenova/whisper-tiny) + [Transformers.js](https://github.com/huggingface/transformers.js), no server, no FFmpeg, no API key required. Now with **Smart Hybrid Mode** to support native `.vtt` and `.srt` files!
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## ✨ Features
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
9
|
+
- 🎯 **Smart Hybrid Mode** — Automatically detects existing `.vtt` or `.srt` tracks from the player, bypassing AI generation to save bandwidth while taking over custom rendering and translation.
|
|
10
|
+
- 🧠 **100% client-side** — AI transcription runs in a Web Worker, never leaves the browser.
|
|
11
|
+
- ⚡ **Streaming transcription** — subtitles appear chunk by chunk while the model processes audio.
|
|
12
|
+
- 🌍 **Auto-translation** — translate subtitles (both AI-generated and native imported ones) via [MyMemory API](https://mymemory.translated.net/) (free, no key) or self-hosted [LibreTranslate](https://libretranslate.com/) / MarianMT (no limits).
|
|
13
|
+
- 🖱️ **Draggable subtitles** — reposition subtitles anywhere on the video (mouse & touch).
|
|
14
|
+
- 📱 **Responsive text** — font size adapts automatically to screen size via `clamp()`.
|
|
15
|
+
- 💾 **Persistent Cache** — transcriptions **and translations** are cached in `localStorage` and reloaded instantly on the next visit.
|
|
16
|
+
- 🔌 **Zero dependencies** — works with the standard myetv-player plugin system.
|
|
17
|
+
- 🔒 **CSP-safe** — compatible with strict Content Security Policies (`worker-src blob:`).
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
@@ -21,18 +22,15 @@
|
|
|
21
22
|
|
|
22
23
|
### Option A — Script tag
|
|
23
24
|
```html
|
|
24
|
-
```
|
|
25
|
-
|
|
26
25
|
<script src="myetv-autosub-plugin.js"></script>
|
|
27
26
|
|
|
28
27
|
```
|
|
29
|
-
```
|
|
30
|
-
|
|
31
28
|
|
|
32
29
|
### Option B — Via npm (if bundled with myetv-player)
|
|
33
30
|
|
|
34
31
|
```bash
|
|
35
32
|
npm install myetv-autosub-plugin
|
|
33
|
+
|
|
36
34
|
```
|
|
37
35
|
|
|
38
36
|
> ⚠️ The plugin must be loaded **after** `myetv-player`.
|
|
@@ -42,52 +40,125 @@ npm install myetv-autosub-plugin
|
|
|
42
40
|
## 🚀 Quick Start
|
|
43
41
|
|
|
44
42
|
```html
|
|
45
|
-
<!-- 1. Load the player -->
|
|
46
|
-
```
|
|
47
|
-
|
|
48
43
|
<script src="myetv-player.min.js"></script>
|
|
49
44
|
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
<!-- 2. Load the plugin -->
|
|
53
|
-
```
|
|
54
|
-
|
|
55
45
|
<script src="myetv-autosub-plugin.js"></script>
|
|
56
46
|
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
<!-- 3. Initialize -->
|
|
60
47
|
<script>
|
|
61
48
|
const player = new MYETVvideoplayer('my-video', { /* player options */ });
|
|
62
49
|
|
|
63
50
|
player.on('playerready', () => {
|
|
64
51
|
player.usePlugin('autoSubtitles', {
|
|
65
|
-
language: 'en',
|
|
52
|
+
language: 'en', // source language of the video
|
|
66
53
|
modelSize: 'tiny', // whisper model size
|
|
67
54
|
position: 'topbar', // button position
|
|
68
55
|
idCache: 'video-123', // stable cache key (recommended)
|
|
69
56
|
});
|
|
70
57
|
});
|
|
71
58
|
</script>
|
|
72
|
-
```
|
|
73
59
|
|
|
60
|
+
```
|
|
74
61
|
|
|
75
62
|
---
|
|
76
63
|
|
|
77
64
|
## ⚙️ Options
|
|
78
65
|
|
|
79
66
|
| Option | Type | Default | Description |
|
|
80
|
-
|
|
|
81
|
-
| `language` | `string` | `null` | Source language of the video audio. Accepts ISO codes (`'it'`, `'en'`) or full names (`'italian'`, `'english'`). If `null`, Whisper auto-detects. |
|
|
67
|
+
| --- | --- | --- | --- |
|
|
68
|
+
| `language` | `string` | `null` | Source language of the video audio. Accepts ISO codes (`'it'`, `'en'`) or full names (`'italian'`, `'english'`). If `null`, Whisper auto-detects. *(If native tracks are detected, inherits the track language automatically).* |
|
|
82
69
|
| `modelSize` | `string` | `'base'` | Whisper model size: `'tiny'` (~39 MB), `'base'` (~74 MB), `'small'` (~244 MB). Larger = more accurate but slower. |
|
|
83
|
-
| `autoGenerate` | `boolean` | `false` | Automatically start transcription and show subtitles when the video player is loaded. |
|
|
70
|
+
| `autoGenerate` | `boolean` | `false` | Automatically start transcription and show subtitles when the video player is loaded. *(Ignored if native tracks are detected).* |
|
|
84
71
|
| `autoTranslation` | `string` | `null` | Automatically translate subtitles into this language on load (ISO 2-letter code, e.g. `'en'`, `'it'`, `'fr'`). Requires subtitles to be ready first. |
|
|
72
|
+
| `translationEngine` | `object` | `null` | Translation engine configuration. If `null`, uses MyMemory (free, limited). See [Translation Engines](https://www.google.com/search?q=%23-translation-engines) below. |
|
|
85
73
|
| `showButton` | `boolean` | `true` | Show the CC button in the player interface. |
|
|
86
74
|
| `position` | `string` | `'topbar'` | Button position: `'right'` or `'left'` (control bar) or `'topbar'` (next to the ⚙️ settings icon). |
|
|
87
75
|
| `subtitleStyle` | `object` | `{}` | Custom inline CSS style object applied to the subtitle text element. |
|
|
88
|
-
| `cacheEnabled` | `boolean` | `true` | Cache transcription in `localStorage` for instant reload. |
|
|
76
|
+
| `cacheEnabled` | `boolean` | `true` | Cache transcription and translations in `localStorage` for instant reload. |
|
|
89
77
|
| `idCache` | `string` | `null` | Stable unique key used for the localStorage cache entry (recommended: your video's database ID). Falls back to a hash of the video URL if not set. |
|
|
90
78
|
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🌐 Translation Engines
|
|
82
|
+
|
|
83
|
+
The plugin supports three translation backends. By default it uses **MyMemory** (no configuration needed).
|
|
84
|
+
|
|
85
|
+
### Comparison
|
|
86
|
+
|
|
87
|
+
| Engine | Limit | Privacy | Setup |
|
|
88
|
+
| --- | --- | --- | --- |
|
|
89
|
+
| **MyMemory** (default) | ⚠️ ~5.000 chars/day per IP | ☁️ External API | None |
|
|
90
|
+
| **LibreTranslate** (self-hosted) | ✅ No limits | 🔒 Your server | Easy |
|
|
91
|
+
| **MarianMT** (self-hosted) | ✅ No limits | 🔒 Your server | Medium |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### MyMemory (default)
|
|
96
|
+
|
|
97
|
+
Free public API, no configuration required. Limit is approximately **5.000 characters/day per IP**. Suitable for low-traffic or development use.
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
// Default — no translationEngine needed
|
|
101
|
+
player.usePlugin('autoSubtitles', {
|
|
102
|
+
autoTranslation: 'it',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### LibreTranslate (recommended for production)
|
|
110
|
+
|
|
111
|
+
Self-hosted, no limits, supports **native array batch translation** — each subtitle segment is translated individually with guaranteed 1:1 index mapping, eliminating any desync issues.
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
player.usePlugin('autoSubtitles', {
|
|
115
|
+
autoTranslation: 'it',
|
|
116
|
+
translationEngine: {
|
|
117
|
+
type: 'libretranslate',
|
|
118
|
+
url: '[https://your-libretranslate-server.com](https://your-libretranslate-server.com)',
|
|
119
|
+
apiKey: 'your-api-key', // optional, required if --api-keys is enabled
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Self-hosting with Docker:**
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
docker run -p 5000:5000 libretranslate/libretranslate --disable-web-ui --api-keys
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
LibreTranslate: [https://github.com/LibreTranslate/LibreTranslate](https://github.com/LibreTranslate/LibreTranslate)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### MarianMT
|
|
137
|
+
|
|
138
|
+
Custom self-hosted REST endpoint compatible with MarianMT or Argos Translate.
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
player.usePlugin('autoSubtitles', {
|
|
142
|
+
autoTranslation: 'it',
|
|
143
|
+
translationEngine: {
|
|
144
|
+
type: 'marianmt',
|
|
145
|
+
url: '[https://your-marianmt-server.com](https://your-marianmt-server.com)',
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Expected request/response format:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
// POST /translate
|
|
155
|
+
// Request
|
|
156
|
+
{ "q": "Hello world", "source": "en", "target": "it" }
|
|
157
|
+
|
|
158
|
+
// Response
|
|
159
|
+
{ "translatedText": "Ciao mondo" }
|
|
160
|
+
|
|
161
|
+
```
|
|
91
162
|
|
|
92
163
|
---
|
|
93
164
|
|
|
@@ -97,27 +168,27 @@ Clicking the **CC button** opens a context menu with three sections:
|
|
|
97
168
|
|
|
98
169
|
### Subtitles
|
|
99
170
|
|
|
100
|
-
|
|
101
|
-
|
|
171
|
+
* **▶ Enable** — show subtitles
|
|
172
|
+
* **✕ Disable** — hide subtitles
|
|
102
173
|
|
|
174
|
+
### Management
|
|
103
175
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
- **🎙️ Generate subtitles** — start transcription (first time)
|
|
107
|
-
- **🔄 Regenerate trnascription** — force re-transcription (discards cache)
|
|
108
|
-
- **📊 Show progress** — open the transcription progress panel (visible while generating)
|
|
176
|
+
*(Note: This section is automatically hidden if native subtitles `.vtt`/`.srt` are detected and imported from the player).*
|
|
109
177
|
|
|
178
|
+
* **🎙️ Generate subtitles** — start transcription (first time)
|
|
179
|
+
* **🔄 Regenerate transcription** — force re-transcription (discards cache)
|
|
180
|
+
* **📊 Show progress** — open the transcription progress panel (visible while generating)
|
|
110
181
|
|
|
111
182
|
### Translate to
|
|
112
183
|
|
|
113
|
-
Scrollable list of 17+ languages. Translations are cached in
|
|
184
|
+
Scrollable list of 17+ languages. Translations are cached in `localStorage` for the session — switching back to a previously translated language is completely instant and requires no API calls.
|
|
114
185
|
|
|
115
186
|
---
|
|
116
187
|
|
|
117
188
|
## 🌍 Supported Translation Languages
|
|
118
189
|
|
|
119
190
|
| Code | Language |
|
|
120
|
-
|
|
|
191
|
+
| --- | --- |
|
|
121
192
|
| `off` | No translation (original) |
|
|
122
193
|
| `it` | 🇮🇹 Italiano |
|
|
123
194
|
| `en` | 🇬🇧 English |
|
|
@@ -136,13 +207,12 @@ Scrollable list of 17+ languages. Translations are cached in memory for the sess
|
|
|
136
207
|
| `uk` | 🇺🇦 Українська |
|
|
137
208
|
| `hi` | 🇮🇳 हिन्दी |
|
|
138
209
|
|
|
139
|
-
|
|
140
210
|
---
|
|
141
211
|
|
|
142
212
|
## 🧠 Whisper Models
|
|
143
213
|
|
|
144
214
|
| Size | ID | Download | Speed | Accuracy |
|
|
145
|
-
|
|
|
215
|
+
| --- | --- | --- | --- | --- |
|
|
146
216
|
| `tiny` | `Xenova/whisper-tiny` | ~39 MB | ⚡⚡⚡ Fast | ★☆☆ Good |
|
|
147
217
|
| `base` | `Xenova/whisper-base` | ~74 MB | ⚡⚡ Medium | ★★☆ Better |
|
|
148
218
|
| `small` | `Xenova/whisper-small` | ~244 MB | ⚡ Slow | ★★★ Best |
|
|
@@ -158,21 +228,28 @@ If your site uses a Content Security Policy, add the following:
|
|
|
158
228
|
```http
|
|
159
229
|
Content-Security-Policy:
|
|
160
230
|
worker-src blob:;
|
|
161
|
-
script-src blob: https://cdn.jsdelivr.net;
|
|
162
|
-
connect-src https://cdn.jsdelivr.net
|
|
231
|
+
script-src blob: [https://cdn.jsdelivr.net](https://cdn.jsdelivr.net);
|
|
232
|
+
connect-src [https://cdn.jsdelivr.net](https://cdn.jsdelivr.net)
|
|
233
|
+
[https://huggingface.co](https://huggingface.co)
|
|
234
|
+
[https://api.mymemory.translated.net](https://api.mymemory.translated.net)
|
|
235
|
+
[https://your-libretranslate-server.com](https://your-libretranslate-server.com);
|
|
236
|
+
|
|
163
237
|
```
|
|
164
|
-
|
|
238
|
+
|
|
239
|
+
> If using LibreTranslate or MarianMT, replace `https://api.mymemory.translated.net` with your server URL (or add both if using MyMemory as fallback).
|
|
240
|
+
|
|
241
|
+
Use `crossorigin="anonymous"` on the `<video>` element.
|
|
165
242
|
|
|
166
243
|
---
|
|
167
244
|
|
|
168
245
|
## 💾 Cache
|
|
169
246
|
|
|
170
|
-
Transcriptions are stored in `localStorage` under the key `myetv_autosub_<id>`.
|
|
247
|
+
Transcriptions and Translations are stored in `localStorage` under the key `myetv_autosub_<id>`.
|
|
171
248
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
249
|
+
* Up to **30 entries** are kept — oldest are evicted automatically
|
|
250
|
+
* Set `idCache: 'your-video-id'` for a stable, human-readable key
|
|
251
|
+
* Set `cacheEnabled: false` to disable caching entirely
|
|
252
|
+
* Use **"🔄 Regenerate transcription"** from the menu to force a fresh AI transcription
|
|
176
253
|
|
|
177
254
|
---
|
|
178
255
|
|
|
@@ -180,8 +257,8 @@ Transcriptions are stored in `localStorage` under the key `myetv_autosub_<id>`.
|
|
|
180
257
|
|
|
181
258
|
Hover over the subtitle text to reveal the **⠿ sposta** drag handle. Click and drag to reposition subtitles anywhere inside the video frame.
|
|
182
259
|
|
|
183
|
-
|
|
184
|
-
|
|
260
|
+
* Works with mouse and touch (touch drag handle is hidden on mobile — drag directly on the subtitle text)
|
|
261
|
+
* Position resets on page reload (not persisted)
|
|
185
262
|
|
|
186
263
|
---
|
|
187
264
|
|
|
@@ -190,35 +267,47 @@ Hover over the subtitle text to reveal the **⠿ sposta** drag handle. Click and
|
|
|
190
267
|
```javascript
|
|
191
268
|
player.on('playerready', () => {
|
|
192
269
|
player.usePlugin('autoSubtitles', {
|
|
193
|
-
language: 'english',
|
|
194
|
-
modelSize: 'base',
|
|
195
|
-
autoGenerate: true,
|
|
196
|
-
autoTranslation: '
|
|
197
|
-
position: 'topbar',
|
|
198
|
-
cacheEnabled: true,
|
|
199
|
-
idCache: `video-${videoId}`,
|
|
270
|
+
language: 'english', // language of the audio: "english" or "en" (if empty auto-detect)
|
|
271
|
+
modelSize: 'base', // AI model: 'tiny', 'base', 'small'
|
|
272
|
+
autoGenerate: true, // start transcription automatically
|
|
273
|
+
autoTranslation: 'it', // auto-translate to Italian when ready
|
|
274
|
+
position: 'topbar', // CC button position: 'left', 'right', 'topbar'
|
|
275
|
+
cacheEnabled: true,
|
|
276
|
+
idCache: `video-${videoId}`,
|
|
277
|
+
translationEngine: {
|
|
278
|
+
type: 'libretranslate',
|
|
279
|
+
url: '[https://your-libretranslate-server.com](https://your-libretranslate-server.com)',
|
|
280
|
+
apiKey: 'your-api-key',
|
|
281
|
+
},
|
|
200
282
|
subtitleStyle: {
|
|
201
283
|
fontSize: '1.2em',
|
|
202
284
|
color: '#ffe066',
|
|
203
285
|
},
|
|
204
286
|
});
|
|
205
287
|
});
|
|
206
|
-
```
|
|
207
288
|
|
|
289
|
+
```
|
|
208
290
|
|
|
209
291
|
---
|
|
210
292
|
|
|
211
293
|
## 🏗️ How It Works
|
|
212
294
|
|
|
213
295
|
```
|
|
214
|
-
User clicks CC
|
|
296
|
+
User clicks CC (or Auto-Trigger starts)
|
|
297
|
+
│
|
|
298
|
+
▼
|
|
299
|
+
Check for Native Player Subtitles (.vtt / .srt)
|
|
300
|
+
│
|
|
301
|
+
Yes ──────────────────────► Import seamlessly, take over UI rendering, skip AI.
|
|
302
|
+
│
|
|
303
|
+
No
|
|
215
304
|
│
|
|
216
305
|
▼
|
|
217
306
|
Check localStorage cache
|
|
218
307
|
│
|
|
219
|
-
|
|
308
|
+
Hit ──────────────────────► Load transcriptions instantly
|
|
220
309
|
│
|
|
221
|
-
|
|
310
|
+
Miss
|
|
222
311
|
│
|
|
223
312
|
▼
|
|
224
313
|
Extract audio (fetch or captureStream)
|
|
@@ -237,8 +326,12 @@ Whisper ASR — chunks of 30s each
|
|
|
237
326
|
│ (subtitles stream in live)
|
|
238
327
|
▼
|
|
239
328
|
Save to localStorage cache
|
|
240
|
-
|
|
329
|
+
│
|
|
330
|
+
▼
|
|
331
|
+
Translate via selected engine
|
|
332
|
+
(MyMemory / LibreTranslate / MarianMT)
|
|
241
333
|
|
|
334
|
+
```
|
|
242
335
|
|
|
243
336
|
---
|
|
244
337
|
|
|
@@ -255,6 +348,3 @@ Pull requests welcome. For major changes, please open an issue first.
|
|
|
255
348
|
---
|
|
256
349
|
|
|
257
350
|
> Built with ❤️ from the [MYETV](https://www.myetv.tv) team.
|
|
258
|
-
|
|
259
|
-
```
|
|
260
|
-
```
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
ini_set('memory_limit', '512M');
|
|
3
|
+
ini_set('max_execution_time', '60');
|
|
4
|
+
//error_reporting(0);
|
|
5
|
+
//ini_set('display_errors', 0);
|
|
6
|
+
header('Content-Type: application/json');
|
|
7
|
+
|
|
8
|
+
$stationsFile = __DIR__ . '/stations_example.json';
|
|
9
|
+
|
|
10
|
+
// Check 1: file exists and is readable
|
|
11
|
+
if (!file_exists($stationsFile) || !is_readable($stationsFile)) {
|
|
12
|
+
http_response_code(503);
|
|
13
|
+
echo json_encode(['error' => 'stations.json not found', 'path' => $stationsFile]);
|
|
14
|
+
exit;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$raw = file_get_contents($stationsFile);
|
|
18
|
+
|
|
19
|
+
// Check 2: file was read correctly
|
|
20
|
+
if ($raw === false || empty($raw)) {
|
|
21
|
+
http_response_code(503);
|
|
22
|
+
echo json_encode(['error' => 'stations.json is empty or unreadable']);
|
|
23
|
+
exit;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
$stations = json_decode($raw, true);
|
|
27
|
+
|
|
28
|
+
// Check 3: JSON is valid
|
|
29
|
+
if (!is_array($stations)) {
|
|
30
|
+
http_response_code(503);
|
|
31
|
+
echo json_encode(['error' => 'stations.json invalid JSON', 'json_error' => json_last_error_msg()]);
|
|
32
|
+
exit;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Common filters (used by all endpoints)
|
|
36
|
+
$countryFilter = isset($_GET['country']) ? strtolower(trim($_GET['country'])) : '';
|
|
37
|
+
$languageFilter = isset($_GET['language']) ? strtolower(trim($_GET['language'])) : '';
|
|
38
|
+
|
|
39
|
+
// All light - lightweight station list (DEFAULT if no other params)
|
|
40
|
+
if (isset($_GET['channel']) && $_GET['channel'] === 'all_light') {
|
|
41
|
+
$offset = (int)($_GET['offset'] ?? 0);
|
|
42
|
+
$limit = (int)($_GET['limit'] ?? 50000);
|
|
43
|
+
|
|
44
|
+
$light = array_map(fn($s) => [
|
|
45
|
+
'channel_id' => $s['channel_id'] ?? 0,
|
|
46
|
+
'name' => $s['name'] ?? '',
|
|
47
|
+
'url_resolved' => $s['url_resolved'] ?? ($s['url'] ?? ''),
|
|
48
|
+
'countrycode' => $s['countrycode'] ?? '',
|
|
49
|
+
'language' => $s['language'] ?? '',
|
|
50
|
+
'tags' => $s['tags'] ?? '',
|
|
51
|
+
'favicon' => $s['favicon'] ?? '',
|
|
52
|
+
], $stations);
|
|
53
|
+
|
|
54
|
+
// Remove stations without URL
|
|
55
|
+
$light = array_values(array_filter(
|
|
56
|
+
$light,
|
|
57
|
+
fn($s) => !empty($s['url_resolved'])
|
|
58
|
+
));
|
|
59
|
+
|
|
60
|
+
// Country filter (exact match on countrycode)
|
|
61
|
+
if ($countryFilter) {
|
|
62
|
+
$light = array_values(array_filter(
|
|
63
|
+
$light,
|
|
64
|
+
fn($s) => strtolower($s['countrycode'] ?? '') === $countryFilter
|
|
65
|
+
));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Language filter (substring match in language OR tags)
|
|
69
|
+
if ($languageFilter) {
|
|
70
|
+
$light = array_values(array_filter(
|
|
71
|
+
$light,
|
|
72
|
+
fn($s) =>
|
|
73
|
+
str_contains(strtolower($s['language'] ?? ''), $languageFilter) ||
|
|
74
|
+
str_contains(strtolower($s['tags'] ?? ''), $languageFilter)
|
|
75
|
+
));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
echo json_encode(array_values(array_slice($light, $offset, $limit)));
|
|
79
|
+
exit;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Single channel by number
|
|
83
|
+
if (isset($_GET['channel']) && is_numeric($_GET['channel'])) {
|
|
84
|
+
$ch = (int)$_GET['channel'];
|
|
85
|
+
$result = array_values(array_filter($stations, fn($s) => ($s['channel_id'] ?? 0) === $ch));
|
|
86
|
+
echo json_encode($result[0] ?? null);
|
|
87
|
+
exit;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Search by name
|
|
91
|
+
if (isset($_GET['search'])) {
|
|
92
|
+
$q = strtolower(trim($_GET['search']));
|
|
93
|
+
|
|
94
|
+
$result = array_values(array_filter($stations,
|
|
95
|
+
fn($s) => str_contains(strtolower($s['name'] ?? ''), $q)
|
|
96
|
+
));
|
|
97
|
+
|
|
98
|
+
// Apply country/language filters to search results too
|
|
99
|
+
if ($countryFilter) {
|
|
100
|
+
$result = array_values(array_filter(
|
|
101
|
+
$result,
|
|
102
|
+
fn($s) => strtolower($s['countrycode'] ?? '') === $countryFilter
|
|
103
|
+
));
|
|
104
|
+
}
|
|
105
|
+
if ($languageFilter) {
|
|
106
|
+
$result = array_values(array_filter(
|
|
107
|
+
$result,
|
|
108
|
+
fn($s) =>
|
|
109
|
+
str_contains(strtolower($s['language'] ?? ''), $languageFilter) ||
|
|
110
|
+
str_contains(strtolower($s['tags'] ?? ''), $languageFilter)
|
|
111
|
+
));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
echo json_encode(array_slice($result, 0, 20));
|
|
115
|
+
exit;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// NO PARAMETERS = return entire list (lightweight)
|
|
119
|
+
$offset = (int)($_GET['offset'] ?? 0);
|
|
120
|
+
$limit = (int)($_GET['limit'] ?? 50000);
|
|
121
|
+
|
|
122
|
+
$light = array_map(fn($s) => [
|
|
123
|
+
'channel_id' => $s['channel_id'] ?? 0,
|
|
124
|
+
'name' => $s['name'] ?? '',
|
|
125
|
+
'url_resolved' => $s['url_resolved'] ?? ($s['url'] ?? ''),
|
|
126
|
+
'countrycode' => $s['countrycode'] ?? '',
|
|
127
|
+
'language' => $s['language'] ?? '',
|
|
128
|
+
'tags' => $s['tags'] ?? '',
|
|
129
|
+
'favicon' => $s['favicon'] ?? '',
|
|
130
|
+
], $stations);
|
|
131
|
+
|
|
132
|
+
// Remove stations without URL
|
|
133
|
+
$light = array_values(array_filter(
|
|
134
|
+
$light,
|
|
135
|
+
fn($s) => !empty($s['url_resolved'])
|
|
136
|
+
));
|
|
137
|
+
|
|
138
|
+
// Apply filters if specified
|
|
139
|
+
if ($countryFilter) {
|
|
140
|
+
$light = array_values(array_filter(
|
|
141
|
+
$light,
|
|
142
|
+
fn($s) => strtolower($s['countrycode'] ?? '') === $countryFilter
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
if ($languageFilter) {
|
|
146
|
+
$light = array_values(array_filter(
|
|
147
|
+
$light,
|
|
148
|
+
fn($s) =>
|
|
149
|
+
str_contains(strtolower($s['language'] ?? ''), $languageFilter) ||
|
|
150
|
+
str_contains(strtolower($s['tags'] ?? ''), $languageFilter)
|
|
151
|
+
));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
echo json_encode(array_values(array_slice($light, $offset, $limit)));
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|