tizenos-iptv 0.2.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 ADDED
@@ -0,0 +1,34 @@
1
+ # TizenBrew IPTV
2
+
3
+ IPTV app module for TizenBrew with QR-based playlist setup.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm i tizenbrew-iptv
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - No bundled playlist URL
14
+ - QR setup flow for entering an M3U URL from a phone
15
+ - Saves the submitted playlist URL in TV `localStorage`
16
+ - Loads the saved playlist automatically on later launches
17
+ - Channel list, search, and TV remote friendly controls
18
+
19
+ ## Controls
20
+
21
+ - Up/Down: move channel list
22
+ - Enter: play selected channel
23
+ - Play/Pause: toggle playback
24
+ - Stop: stop stream
25
+ - Red key: show setup QR
26
+ - Green key: reload playlist
27
+ - Yellow key: change playlist
28
+
29
+ ## Maintainer Publish
30
+
31
+ ```bash
32
+ npm run pack:check
33
+ npm run publish:public
34
+ ```
package/dist/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # IPTV Player
2
+
3
+ TizenBrew IPTV player based on `@kv8n2oryk/iptv-player`, with playlist URL setup through QR code instead of a bundled M3U URL.
package/dist/inject.js ADDED
@@ -0,0 +1,939 @@
1
+ (function() {
2
+ var I = "";
3
+ var W = "https://tizenbrew-iptv-setup.dvt-kisu.workers.dev";
4
+ var LS = "tizenbrewIptv:playlistUrl";
5
+ var setupCode = "";
6
+ var setupTimer = null;
7
+ var i = [], p = 0, v = "", w = null, S = 1e4, b = null, V = !1, a = null, h = null, k = null, T = null;
8
+
9
+ // ===== ADDED: DASH player instance =====
10
+ var dashPlayer = null;
11
+ var isDashStream = false;
12
+
13
+ var B = {
14
+ 13: "Enter",
15
+ 27: "Escape",
16
+ 32: " ",
17
+ 37: "ArrowLeft",
18
+ 38: "ArrowUp",
19
+ 39: "ArrowRight",
20
+ 40: "ArrowDown",
21
+ 10009: "Escape",
22
+ 10190: "MediaPlayPause",
23
+ 10252: "MediaPlayPause",
24
+ 405: "ColorF2Yellow",
25
+ 413: "MediaStop",
26
+ 427: "ChannelUp",
27
+ 428: "ChannelDown",
28
+ 447: "VolumeUp",
29
+ 448: "VolumeDown"
30
+ };
31
+
32
+ function M() {
33
+ var e = document.createElement("style");
34
+ e.textContent = '*{margin:0;padding:0;box-sizing:border-box}body{background:#1a1a1a;color:#fff;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;height:100vh;overflow:hidden}#app{display:flex;flex-direction:column;height:100vh}header{display:flex;justify-content:space-between;align-items:center;padding:12px 24px;background:#111;border-bottom:1px solid #333}header h1{font-size:28px;font-weight:600}#main{display:flex;flex:1;overflow:hidden}#player-wrap{flex:1;display:flex;flex-direction:column;justify-content:center;align-items:center;background:#000;min-width:0}#player{width:100%;height:70vh;max-height:70vh;background:#000}#list{width:320px;background:#111;border-left:1px solid #333;overflow-y:auto;padding:8px 0}.grp{padding:8px 16px 4px;font-size:18px;font-weight:600;color:#888;text-transform:uppercase;letter-spacing:1px}.ch{display:block;width:100%;padding:12px 16px;border:none;background:none;color:#fff;font-size:24px;text-align:left;cursor:pointer}.ch:hover,.ch:focus{background:#333;outline:none}.ch.on{background:#2a2a2a;border-left:4px solid #ffd600;padding-left:12px}.ch:focus{outline:3px solid #ffd600;outline-offset:-3px}#bar{display:flex;justify-content:space-between;align-items:center;padding:10px 24px;background:#111;border-top:1px solid #333;font-size:20px}#now{color:#ffd600;font-weight:500}#st{color:#888}#list::-webkit-scrollbar{width:8px}#list::-webkit-scrollbar-track{background:#1a1a1a}#list::-webkit-scrollbar-thumb{background:#555;border-radius:4px}.menu-item{border-top:2px solid #333;margin-top:8px;color:#ffd600;font-weight:600}#app.fullscreen-mode header,#app.fullscreen-mode #list,#app.fullscreen-mode #bar{display:none}#app.fullscreen-mode #main{display:block;height:100vh}#app.fullscreen-mode #player-wrap{width:100vw;height:100vh}#app.fullscreen-mode #player{width:100vw;height:100vh;max-height:none}', document.head.appendChild(e);
35
+ }
36
+
37
+ function U() {
38
+ M(), document.body.innerHTML = '<div id="app"><header><h1>IPTV Player</h1><span id="cnt"></span></header><div id="main"><div id="player-wrap"><video id="player" playsinline controls></video></div><aside id="list"></aside></div><footer id="bar"><span id="now">No channel selected</span><span id="st">Ready</span></footer></div>', a = document.getElementById("player"), h = document.getElementById("list"), T = document.getElementById("now"), k = document.getElementById("st");
39
+
40
+ // ===== ADDED: Initialize dash.js player =====
41
+ if (typeof dashjs !== 'undefined') {
42
+ try {
43
+ dashPlayer = dashjs.MediaPlayer().create();
44
+ dashPlayer.initialize(a, null, true);
45
+ dashPlayer.updateSettings({
46
+ 'debug': {
47
+ 'logLevel': dashjs.Debug.LOG_LEVEL_NONE
48
+ }
49
+ });
50
+ } catch (e) {
51
+ console.warn('dash.js init error:', e.message);
52
+ }
53
+ }
54
+ }
55
+
56
+ function d(e) {
57
+ k && (k.textContent = e);
58
+ }
59
+
60
+ function E(e) {
61
+ T && (T.textContent = e);
62
+ }
63
+
64
+ function H() {
65
+ var e = document.getElementById("app");
66
+ e && (e.className = "fullscreen-mode");
67
+ }
68
+
69
+ function F() {
70
+ var e = document.getElementById("app");
71
+ e && (e.className = "");
72
+ }
73
+
74
+ function g() {
75
+ b && clearTimeout(b), F(), V && (b = setTimeout(H, S));
76
+ }
77
+
78
+ function makeSetupCode() {
79
+ var chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
80
+ var code = "";
81
+ for (var idx = 0; idx < 6; idx++) code += chars[Math.floor(Math.random() * chars.length)];
82
+ return code;
83
+ }
84
+
85
+ function setupUrl(code) {
86
+ return W + "/setup?code=" + encodeURIComponent(code);
87
+ }
88
+
89
+ function xhrGetText(url, done) {
90
+ var req = new XMLHttpRequest();
91
+ req.open("GET", url, true);
92
+ req.onreadystatechange = function() {
93
+ if (req.readyState !== 4) return;
94
+ done(null, req.status, req.responseText);
95
+ };
96
+ req.onerror = function() { done(new Error("Network failed"), 0, ""); };
97
+ try { req.send(); } catch (e) { done(e, 0, ""); }
98
+ }
99
+
100
+ function savePlaylistUrl(url) {
101
+ I = url;
102
+ try { localStorage.setItem(LS, url); } catch (err) {}
103
+ }
104
+
105
+ function clearSetupTimer() {
106
+ if (setupTimer) clearInterval(setupTimer);
107
+ setupTimer = null;
108
+ }
109
+
110
+ // ===== ADDED: Check if URL is a DASH manifest =====
111
+ function isDashUrl(url) {
112
+ if (!url) return false;
113
+ var lower = url.toLowerCase();
114
+ return lower.endsWith('.mpd') || (lower.includes('manifest') && lower.includes('.mpd'));
115
+ }
116
+
117
+ // ===== ADDED: Stop any currently playing DASH stream =====
118
+ function stopDash() {
119
+ if (dashPlayer) {
120
+ try {
121
+ dashPlayer.reset();
122
+ } catch (e) {}
123
+ }
124
+ isDashStream = false;
125
+ }
126
+
127
+ // ===== MODIFIED: Play channel with DASH support =====
128
+ function y(e) {
129
+ if (!(e < 0 || e >= i.length || !a)) {
130
+ V = !0, p = e;
131
+ var n = i[e];
132
+ D(e), E("Now Playing: " + n.name), d("Loading stream...");
133
+
134
+ // Stop any existing DASH stream
135
+ stopDash();
136
+
137
+ var url = n.url;
138
+ isDashStream = isDashUrl(url);
139
+
140
+ if (isDashStream && dashPlayer) {
141
+ // Use dash.js for DASH streams
142
+ try {
143
+ dashPlayer.initialize(a, url, true);
144
+ dashPlayer.play();
145
+ d("Playing DASH stream");
146
+
147
+ dashPlayer.on(dashjs.MediaPlayer.events.ERROR, function(err) {
148
+ d("DASH Error: " + (err.error || 'unknown'));
149
+ });
150
+
151
+ dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_STARTED, function() {
152
+ d("Playing");
153
+ });
154
+
155
+ dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, function() {
156
+ d("Stream ended");
157
+ });
158
+
159
+ } catch (err) {
160
+ d("DASH Error: " + (err.message || 'failed'));
161
+ // Fallback to native playback
162
+ isDashStream = false;
163
+ playNative(url);
164
+ }
165
+ } else {
166
+ // Use native video for non-DASH streams
167
+ playNative(url);
168
+ }
169
+
170
+ g();
171
+ }
172
+ }
173
+
174
+ // ===== ADDED: Native playback function (extracted from original y function) =====
175
+ function playNative(url) {
176
+ stopDash();
177
+ a.src = url;
178
+ a.onerror = function() {
179
+ var r = a.error;
180
+ d("Error: " + (r ? r.message || "code " + r.code : "unknown"));
181
+ };
182
+ a.oncanplay = function() {
183
+ if (!isDashStream) d("Playing");
184
+ };
185
+ var t = a.play();
186
+ if (t && t.catch) {
187
+ t.catch(function(r) {
188
+ if (!isDashStream) d("Error: " + (r && r.message ? r.message : "play failed"));
189
+ });
190
+ }
191
+ }
192
+
193
+ var QR_CODE_CAPACITY = [[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
194
+
195
+ function QRMaxChars(text, level) {
196
+ var b = encodeURI(text).toString().replace(/%[0-9a-fA-F]{2}/g, "a");
197
+ var len = b.length + (b.length !== text.length ? 3 : 0);
198
+ for (var v = 0; v < QR_CODE_CAPACITY.length; v++) {
199
+ var cap = 0;
200
+ switch (level) {
201
+ case 1: cap = QR_CODE_CAPACITY[v][0]; break;
202
+ case 0: cap = QR_CODE_CAPACITY[v][1]; break;
203
+ case 3: cap = QR_CODE_CAPACITY[v][2]; break;
204
+ case 2: cap = QR_CODE_CAPACITY[v][3]; break;
205
+ }
206
+ if (cap >= len) return v + 1;
207
+ }
208
+ return 40;
209
+ }
210
+
211
+ function QRCode(el, options) {
212
+ if (typeof el === "string") el = document.getElementById(el);
213
+ var opt = options || {};
214
+ var width = opt.width || 220;
215
+ var height = opt.height || 220;
216
+ var text = opt.text || "";
217
+ var correctLevel = opt.correctLevel || QRCode.CorrectLevel.H;
218
+ var typeNumber = opt.typeNumber || QRMaxChars(text, correctLevel);
219
+
220
+ var qr = new QRCodeModel(typeNumber, correctLevel);
221
+ qr.addData(text);
222
+ qr.make();
223
+
224
+ var moduleCount = qr.getModuleCount();
225
+ var cellSize = Math.max(2, Math.floor(width / moduleCount));
226
+
227
+ el.innerHTML = "";
228
+ var table = document.createElement("table");
229
+ table.style.cssText = "border-collapse:collapse;margin:0 auto;";
230
+ for (var r = 0; r < moduleCount; r++) {
231
+ var tr = document.createElement("tr");
232
+ tr.style.cssText = "height:" + cellSize + "px;padding:0;margin:0;";
233
+ for (var c = 0; c < moduleCount; c++) {
234
+ var td = document.createElement("td");
235
+ td.style.cssText = "width:" + cellSize + "px;height:" + cellSize + "px;padding:0;margin:0;background:" + (qr.isDark(r, c) ? "#000" : "#fff") + ";";
236
+ tr.appendChild(td);
237
+ }
238
+ table.appendChild(tr);
239
+ }
240
+ el.appendChild(table);
241
+ }
242
+ QRCode.CorrectLevel = { L: 1, M: 0, Q: 3, H: 2 };
243
+
244
+ function QRCodeModel(typeNumber, errorCorrectLevel) {
245
+ this.typeNumber = typeNumber;
246
+ this.errorCorrectLevel = errorCorrectLevel;
247
+ this.modules = null;
248
+ this.moduleCount = 0;
249
+ this.dataCache = null;
250
+ this.dataList = [];
251
+ }
252
+
253
+ QRCodeModel.prototype = {
254
+ addData: function(data) { var d = new QR8bitByte(data); this.dataList.push(d); this.dataCache = null; },
255
+ isDark: function(row, col) {
256
+ if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) throw new Error(row + "," + col);
257
+ return this.modules[row][col];
258
+ },
259
+ getModuleCount: function() { return this.moduleCount; },
260
+ make: function() { this.makeImpl(false, this.getBestMaskPattern()); },
261
+ makeImpl: function(test, maskPattern) {
262
+ this.moduleCount = this.typeNumber * 4 + 17;
263
+ this.modules = new Array(this.moduleCount);
264
+ for (var r = 0; r < this.moduleCount; r++) { this.modules[r] = new Array(this.moduleCount); for (var c = 0; c < this.moduleCount; c++) this.modules[r][c] = null; }
265
+ this.setupPositionProbePattern(0, 0);
266
+ this.setupPositionProbePattern(this.moduleCount - 7, 0);
267
+ this.setupPositionProbePattern(0, this.moduleCount - 7);
268
+ this.setupPositionAdjustPattern();
269
+ this.setupTimingPattern();
270
+ this.setupTypeInfo(test, maskPattern);
271
+ if (this.typeNumber >= 7) this.setupTypeNumber(test);
272
+ if (this.dataCache == null) this.dataCache = QRCodeModel.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
273
+ this.mapData(this.dataCache, maskPattern);
274
+ },
275
+ setupPositionProbePattern: function(row, col) {
276
+ for (var r = -1; r <= 7; r++) {
277
+ if (row + r <= -1 || this.moduleCount <= row + r) continue;
278
+ for (var c = -1; c <= 7; c++) {
279
+ if (col + c <= -1 || this.moduleCount <= col + c) continue;
280
+ this.modules[row + r][col + c] = (r >= 0 && r <= 6 && (c === 0 || c === 6)) || (c >= 0 && c <= 6 && (r === 0 || r === 6)) || (r >= 2 && r <= 4 && c >= 2 && c <= 4);
281
+ }
282
+ }
283
+ },
284
+ getBestMaskPattern: function() {
285
+ var minLost = 0, bestMask = 0;
286
+ for (var i = 0; i < 8; i++) {
287
+ this.makeImpl(true, i);
288
+ var lost = QRUtil.getLostPoint(this);
289
+ if (i === 0 || minLost > lost) { minLost = lost; bestMask = i; }
290
+ }
291
+ return bestMask;
292
+ },
293
+ setupTimingPattern: function() {
294
+ for (var r = 8; r < this.moduleCount - 8; r++) { if (this.modules[r][6] == null) this.modules[r][6] = r % 2 === 0; }
295
+ for (var c = 8; c < this.moduleCount - 8; c++) { if (this.modules[6][c] == null) this.modules[6][c] = c % 2 === 0; }
296
+ },
297
+ setupPositionAdjustPattern: function() {
298
+ var pos = QRUtil.getPatternPosition(this.typeNumber);
299
+ for (var i = 0; i < pos.length; i++) {
300
+ for (var j = 0; j < pos.length; j++) {
301
+ var row = pos[i], col = pos[j];
302
+ if (this.modules[row][col] == null) {
303
+ for (var r = -2; r <= 2; r++) {
304
+ for (var c = -2; c <= 2; c++) {
305
+ this.modules[row + r][col + c] = r === -2 || r === 2 || c === -2 || c === 2 || (r === 0 && c === 0);
306
+ }
307
+ }
308
+ }
309
+ }
310
+ }
311
+ },
312
+ setupTypeNumber: function(test) {
313
+ var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
314
+ for (var i = 0; i < 18; i++) {
315
+ var mod = !test && ((bits >> i) & 1) === 1;
316
+ this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
317
+ }
318
+ for (i = 0; i < 18; i++) {
319
+ var mod = !test && ((bits >> i) & 1) === 1;
320
+ this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
321
+ }
322
+ },
323
+ setupTypeInfo: function(test, maskPattern) {
324
+ var data = (this.errorCorrectLevel << 3 | maskPattern);
325
+ var bits = QRUtil.getBCHTypeInfo(data);
326
+ for (var i = 0; i < 15; i++) {
327
+ var mod = !test && ((bits >> i) & 1) === 1;
328
+ if (i < 6) this.modules[i][8] = mod;
329
+ else if (i < 8) this.modules[i + 1][8] = mod;
330
+ else this.modules[this.moduleCount - 15 + i][8] = mod;
331
+ }
332
+ for (i = 0; i < 15; i++) {
333
+ var mod = !test && ((bits >> i) & 1) === 1;
334
+ if (i < 8) this.modules[8][this.moduleCount - i - 1] = mod;
335
+ else if (i < 9) this.modules[8][15 - i - 1 + 1] = mod;
336
+ else this.modules[8][15 - i - 1] = mod;
337
+ }
338
+ this.modules[this.moduleCount - 8][8] = !test;
339
+ },
340
+ mapData: function(data, maskPattern) {
341
+ var inc = -1, row = this.moduleCount - 1, bitIndex = 7, byteIndex = 0;
342
+ for (var col = this.moduleCount - 1; col > 0; col -= 2) {
343
+ if (col === 6) col--;
344
+ while (true) {
345
+ for (var c = 0; c < 2; c++) {
346
+ if (this.modules[row][col - c] == null) {
347
+ var dark = false;
348
+ if (byteIndex < data.length) dark = ((data[byteIndex] >>> bitIndex) & 1) === 1;
349
+ if (QRUtil.getMask(maskPattern, row, col - c)) dark = !dark;
350
+ this.modules[row][col - c] = dark;
351
+ bitIndex--;
352
+ if (bitIndex === -1) { byteIndex++; bitIndex = 7; }
353
+ }
354
+ }
355
+ row += inc;
356
+ if (row < 0 || this.moduleCount <= row) { row -= inc; inc = -inc; break; }
357
+ }
358
+ }
359
+ }
360
+ };
361
+
362
+ QRCodeModel.PAD0 = 236;
363
+ QRCodeModel.PAD1 = 17;
364
+
365
+ QRCodeModel.createData = function(typeNumber, errorCorrectLevel, dataList) {
366
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
367
+ var buffer = new QRBitBuffer();
368
+ for (var i = 0; i < dataList.length; i++) {
369
+ var data = dataList[i];
370
+ buffer.put(data.mode, 4);
371
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
372
+ data.write(buffer);
373
+ }
374
+ var totalDataCount = 0;
375
+ for (i = 0; i < rsBlocks.length; i++) totalDataCount += rsBlocks[i].dataCount;
376
+ if (buffer.getLengthInBits() > totalDataCount * 8) throw new Error("code length overflow: " + buffer.getLengthInBits() + " > " + totalDataCount * 8);
377
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) buffer.put(0, 4);
378
+ while (buffer.getLengthInBits() % 8 !== 0) buffer.putBit(false);
379
+ while (true) {
380
+ if (buffer.getLengthInBits() >= totalDataCount * 8) break;
381
+ buffer.put(QRCodeModel.PAD0, 8);
382
+ if (buffer.getLengthInBits() >= totalDataCount * 8) break;
383
+ buffer.put(QRCodeModel.PAD1, 8);
384
+ }
385
+ return QRCodeModel.createBytes(buffer, rsBlocks);
386
+ };
387
+
388
+ QRCodeModel.createBytes = function(buffer, rsBlocks) {
389
+ var offset = 0, maxDcCount = 0, maxEcCount = 0;
390
+ var dcdata = new Array(rsBlocks.length), ecdata = new Array(rsBlocks.length);
391
+ for (var r = 0; r < rsBlocks.length; r++) {
392
+ var dcCount = rsBlocks[r].dataCount;
393
+ var ecCount = rsBlocks[r].totalCount - dcCount;
394
+ maxDcCount = Math.max(maxDcCount, dcCount);
395
+ maxEcCount = Math.max(maxEcCount, ecCount);
396
+ dcdata[r] = new Array(dcCount);
397
+ for (var i = 0; i < dcdata[r].length; i++) dcdata[r][i] = 255 & buffer.buffer[i + offset];
398
+ offset += dcCount;
399
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
400
+ var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
401
+ var modPoly = rawPoly.mod(rsPoly);
402
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
403
+ for (i = 0; i < ecdata[r].length; i++) {
404
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
405
+ ecdata[r][i] = modIndex >= 0 ? modPoly.get(modIndex) : 0;
406
+ }
407
+ }
408
+ var totalCount = 0;
409
+ for (r = 0; r < rsBlocks.length; r++) totalCount += rsBlocks[r].totalCount;
410
+ var data = new Array(totalCount), index = 0;
411
+ for (i = 0; i < maxDcCount; i++) {
412
+ for (r = 0; r < rsBlocks.length; r++) { if (i < dcdata[r].length) data[index++] = dcdata[r][i]; }
413
+ }
414
+ for (i = 0; i < maxEcCount; i++) {
415
+ for (r = 0; r < rsBlocks.length; r++) { if (i < ecdata[r].length) data[index++] = ecdata[r][i]; }
416
+ }
417
+ return data;
418
+ };
419
+
420
+ function QR8bitByte(data) {
421
+ this.mode = QR8bitByte.MODE_8BIT_BYTE;
422
+ this.data = data;
423
+ this.parsedData = [];
424
+ var byteData = [];
425
+ for (var i = 0; i < data.length; i++) {
426
+ var c = data.charCodeAt(i);
427
+ if (c > 65536) {
428
+ byteData[0] = 240 | ((1835008 & c) >>> 18);
429
+ byteData[1] = 128 | ((258048 & c) >>> 12);
430
+ byteData[2] = 128 | ((4032 & c) >>> 6);
431
+ byteData[3] = 128 | (63 & c);
432
+ } else if (c > 2048) {
433
+ byteData[0] = 224 | ((61440 & c) >>> 12);
434
+ byteData[1] = 128 | ((4032 & c) >>> 6);
435
+ byteData[2] = 128 | (63 & c);
436
+ } else if (c > 128) {
437
+ byteData[0] = 192 | ((1984 & c) >>> 6);
438
+ byteData[1] = 128 | (63 & c);
439
+ } else {
440
+ byteData[0] = c;
441
+ }
442
+ for (var j = 0; j < byteData.length; j++) this.parsedData.push(byteData[j]);
443
+ byteData = [];
444
+ }
445
+ this.parsedData.length !== data.length && (this.parsedData.unshift(191), this.parsedData.unshift(187), this.parsedData.unshift(239));
446
+ }
447
+
448
+ QR8bitByte.MODE_8BIT_BYTE = 4;
449
+
450
+ QR8bitByte.prototype = {
451
+ getLength: function() { return this.parsedData.length; },
452
+ write: function(buffer) { for (var i = 0; i < this.parsedData.length; i++) buffer.put(this.parsedData[i], 8); }
453
+ };
454
+
455
+ function QRPolynomial(num, shift) {
456
+ if (num.length === undefined) throw new Error(num.length + "/" + shift);
457
+ var offset = 0;
458
+ while (offset < num.length && num[offset] === 0) offset++;
459
+ this.num = new Array(num.length - offset + shift);
460
+ for (var i = 0; i < num.length - offset; i++) this.num[i] = num[i + offset];
461
+ }
462
+
463
+ QRPolynomial.prototype = {
464
+ get: function(index) { return this.num[index]; },
465
+ getLength: function() { return this.num.length; },
466
+ multiply: function(e) {
467
+ var num = new Array(this.getLength() + e.getLength() - 1);
468
+ for (var i = 0; i < this.getLength(); i++) {
469
+ for (var j = 0; j < e.getLength(); j++) {
470
+ num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
471
+ }
472
+ }
473
+ return new QRPolynomial(num, 0);
474
+ },
475
+ mod: function(e) {
476
+ if (this.getLength() - e.getLength() < 0) return this;
477
+ var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0));
478
+ var num = new Array(this.getLength());
479
+ for (var i = 0; i < this.getLength(); i++) num[i] = this.get(i);
480
+ for (i = 0; i < e.getLength(); i++) num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
481
+ return new QRPolynomial(num, 0).mod(e);
482
+ }
483
+ };
484
+
485
+ var QRMath = {
486
+ glog: function(n) { if (n < 1) throw new Error("glog(" + n + ")"); return QRMath.LOG_TABLE[n]; },
487
+ gexp: function(n) { while (n < 0) n += 255; while (n >= 256) n -= 255; return QRMath.EXP_TABLE[n]; },
488
+ EXP_TABLE: new Array(256),
489
+ LOG_TABLE: new Array(256)
490
+ };
491
+
492
+ for (var _i = 0; _i < 8; _i++) QRMath.EXP_TABLE[_i] = 1 << _i;
493
+ for (var _i = 8; _i < 256; _i++) QRMath.EXP_TABLE[_i] = QRMath.EXP_TABLE[_i - 4] ^ QRMath.EXP_TABLE[_i - 5] ^ QRMath.EXP_TABLE[_i - 6] ^ QRMath.EXP_TABLE[_i - 8];
494
+ for (var _i = 0; _i < 255; _i++) QRMath.LOG_TABLE[QRMath.EXP_TABLE[_i]] = _i;
495
+
496
+ function QRRSBlock(totalCount, dataCount) { this.totalCount = totalCount; this.dataCount = dataCount; }
497
+
498
+ QRRSBlock.RS_BLOCK_TABLE = [
499
+ [1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],
500
+ [1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],
501
+ [1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],
502
+ [2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],
503
+ [2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],
504
+ [4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],
505
+ [4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],
506
+ [5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],
507
+ [1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],
508
+ [3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],
509
+ [4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],
510
+ [4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],
511
+ [8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],
512
+ [8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],
513
+ [7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],
514
+ [13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],
515
+ [17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],
516
+ [12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],
517
+ [17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],
518
+ [20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]
519
+ ];
520
+
521
+ QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) {
522
+ var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
523
+ if (rsBlock === undefined) throw new Error("bad rs block @typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel);
524
+ var length = rsBlock.length / 3, list = [];
525
+ for (var i = 0; i < length; i++) {
526
+ var count = rsBlock[i * 3 + 0], totalCount = rsBlock[i * 3 + 1], dataCount = rsBlock[i * 3 + 2];
527
+ for (var j = 0; j < count; j++) list.push(new QRRSBlock(totalCount, dataCount));
528
+ }
529
+ return list;
530
+ };
531
+
532
+ QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) {
533
+ var table = QRRSBlock.RS_BLOCK_TABLE;
534
+ switch (errorCorrectLevel) {
535
+ case QRCode.CorrectLevel.L: return table[(typeNumber - 1) * 4 + 0];
536
+ case QRCode.CorrectLevel.M: return table[(typeNumber - 1) * 4 + 1];
537
+ case QRCode.CorrectLevel.Q: return table[(typeNumber - 1) * 4 + 2];
538
+ case QRCode.CorrectLevel.H: return table[(typeNumber - 1) * 4 + 3];
539
+ default: return undefined;
540
+ }
541
+ };
542
+
543
+ var QRUtil = {
544
+ PATTERN_POSITION_TABLE: [[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],
545
+ G15: 1335, G18: 7973, G15_MASK: 21522,
546
+ getBCHTypeInfo: function(data) {
547
+ var d = data << 10;
548
+ while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) d ^= QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15));
549
+ return (data << 10 | d) ^ QRUtil.G15_MASK;
550
+ },
551
+ getBCHTypeNumber: function(data) {
552
+ var d = data << 12;
553
+ while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) d ^= QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18));
554
+ return data << 12 | d;
555
+ },
556
+ getBCHDigit: function(data) { var digit = 0; while (data !== 0) { digit++; data >>>= 1; } return digit; },
557
+ getPatternPosition: function(typeNumber) { return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; },
558
+ getMask: function(maskPattern, i, j) {
559
+ switch (maskPattern) {
560
+ case 0: return (i + j) % 2 === 0;
561
+ case 1: return i % 2 === 0;
562
+ case 2: return j % 3 === 0;
563
+ case 3: return (i + j) % 3 === 0;
564
+ case 4: return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 === 0;
565
+ case 5: return (i * j) % 2 + (i * j) % 3 === 0;
566
+ case 6: return ((i * j) % 2 + (i * j) % 3) % 2 === 0;
567
+ case 7: return ((i * j) % 3 + (i + j) % 2) % 2 === 0;
568
+ default: throw new Error("bad maskPattern:" + maskPattern);
569
+ }
570
+ },
571
+ getErrorCorrectPolynomial: function(errorCorrectLength) {
572
+ var a = new QRPolynomial([1], 0);
573
+ for (var i = 0; i < errorCorrectLength; i++) a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
574
+ return a;
575
+ },
576
+ getLengthInBits: function(mode, type) {
577
+ if (type >= 1 && type < 10) {
578
+ switch (mode) { case 1: return 10; case 2: return 9; case 4: return 8; case 8: return 8; default: throw new Error("mode:" + mode); }
579
+ } else if (type < 27) {
580
+ switch (mode) { case 1: return 12; case 2: return 11; case 4: return 16; case 8: return 10; default: throw new Error("mode:" + mode); }
581
+ } else if (type < 41) {
582
+ switch (mode) { case 1: return 14; case 2: return 13; case 4: return 16; case 8: return 12; default: throw new Error("mode:" + mode); }
583
+ } else {
584
+ throw new Error("type:" + type);
585
+ }
586
+ },
587
+ getLostPoint: function(qrCode) {
588
+ var moduleCount = qrCode.getModuleCount(), lostPoint = 0;
589
+ for (var row = 0; row < moduleCount; row++) {
590
+ for (var col = 0; col < moduleCount; col++) {
591
+ var sameCount = 0, dark = qrCode.isDark(row, col);
592
+ for (var r = -1; r <= 1; r++) {
593
+ if (row + r < 0 || moduleCount <= row + r) continue;
594
+ for (var c = -1; c <= 1; c++) {
595
+ if (col + c < 0 || moduleCount <= col + c) continue;
596
+ if (r === 0 && c === 0) continue;
597
+ if (dark === qrCode.isDark(row + r, col + c)) sameCount++;
598
+ }
599
+ }
600
+ if (sameCount > 5) lostPoint += 3 + sameCount - 5;
601
+ }
602
+ }
603
+ for (row = 0; row < moduleCount - 1; row++) {
604
+ for (col = 0; col < moduleCount - 1; col++) {
605
+ var count = 0;
606
+ if (qrCode.isDark(row, col)) count++;
607
+ if (qrCode.isDark(row + 1, col)) count++;
608
+ if (qrCode.isDark(row, col + 1)) count++;
609
+ if (qrCode.isDark(row + 1, col + 1)) count++;
610
+ if (count === 0 || count === 4) lostPoint += 3;
611
+ }
612
+ }
613
+ for (row = 0; row < moduleCount; row++) {
614
+ for (col = 0; col < moduleCount - 6; col++) {
615
+ if (qrCode.isDark(row, col) && !qrCode.isDark(row, col + 1) && qrCode.isDark(row, col + 2) && qrCode.isDark(row, col + 3) && qrCode.isDark(row, col + 4) && !qrCode.isDark(row, col + 5) && qrCode.isDark(row, col + 6)) lostPoint += 40;
616
+ }
617
+ }
618
+ for (col = 0; col < moduleCount; col++) {
619
+ for (row = 0; row < moduleCount - 6; row++) {
620
+ if (qrCode.isDark(row, col) && !qrCode.isDark(row + 1, col) && qrCode.isDark(row + 2, col) && qrCode.isDark(row + 3, col) && qrCode.isDark(row + 4, col) && !qrCode.isDark(row + 5, col) && qrCode.isDark(row + 6, col)) lostPoint += 40;
621
+ }
622
+ }
623
+ var darkCount = 0;
624
+ for (col = 0; col < moduleCount; col++) { for (row = 0; row < moduleCount; row++) { if (qrCode.isDark(row, col)) darkCount++; } }
625
+ lostPoint += Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5 * 10;
626
+ return lostPoint;
627
+ }
628
+ };
629
+
630
+ function QRBitBuffer() {
631
+ this.buffer = [];
632
+ this.length = 0;
633
+ }
634
+
635
+ QRBitBuffer.prototype = {
636
+ get: function(index) { var bufIndex = Math.floor(index / 8); return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) === 1; },
637
+ put: function(num, length) { for (var i = 0; i < length; i++) this.putBit(((num >>> (length - i - 1)) & 1) === 1); },
638
+ getLengthInBits: function() { return this.length; },
639
+ putBit: function(bit) {
640
+ var bufIndex = Math.floor(this.length / 8);
641
+ if (this.buffer.length <= bufIndex) this.buffer.push(0);
642
+ if (bit) this.buffer[bufIndex] |= 128 >>> (this.length % 8);
643
+ this.length++;
644
+ }
645
+ };
646
+
647
+ function K() {
648
+ if (!I) { showSetup(); return; }
649
+ d("Fetching playlist...");
650
+ var e = new XMLHttpRequest();
651
+ e.open("GET", I, true);
652
+ e.onreadystatechange = function() {
653
+ if (e.readyState === 4) {
654
+ if (e.status >= 200 && e.status < 300) {
655
+ i = z(e.responseText);
656
+ _();
657
+ d("Ready - " + i.length + " channels");
658
+ i.length > 0 ? E("Select a channel") : E("No channels found");
659
+ } else {
660
+ d("Error: HTTP " + e.status);
661
+ }
662
+ }
663
+ };
664
+ e.onerror = function() { d("Error: Network failed"); };
665
+ try { e.send(); } catch (err) { d("Error: " + (err.message || "send failed")); }
666
+ }
667
+
668
+ function z(e) {
669
+ for (var n = e.split(/\r?\n/), t = [], r = 0, o = 0; o < n.length; o++) {
670
+ var c = n[o].trim();
671
+ if (c.indexOf("#EXTINF:") === 0) {
672
+ var u = c.match(/#EXTINF:-?\d+[^,]*,(.*)/);
673
+ if (u) {
674
+ for (var s = u[1].trim(), l = c.match(/group-title="([^"]*)"/), m = c.match(/tvg-logo="([^"]*)"/), f = 1; n[o + f] && n[o + f].trim().indexOf("#") === 0; ) f++;
675
+ var C = n[o + f] ? n[o + f].trim() : "";
676
+ C && C.indexOf("#") !== 0 && (t.push({
677
+ name: s,
678
+ url: C,
679
+ group: l ? l[1] : N(s),
680
+ logo: m ? m[1] : "",
681
+ index: r
682
+ }), r++, o += f);
683
+ }
684
+ }
685
+ }
686
+ return t;
687
+ }
688
+
689
+ function N(e) {
690
+ var n = e.toUpperCase();
691
+ return n.indexOf("VTV") === 0 ? "VTV" : n.indexOf("HTVC") === 0 ? "HTVC" : n.indexOf("HTV") === 0 ? "HTV" : n.indexOf("SCTV") === 0 ? "SCTV" : n.indexOf("THVL") === 0 ? "THVL" : n.indexOf("BTV") === 0 ? "BTV" : n.indexOf("KTV") === 0 ? "KTV" : n.indexOf("NTV") === 0 ? "NTV" : n.indexOf("BBC") === 0 ? "BBC" : n.indexOf("K+") === 0 ? "K+" : n.indexOf("CNN") === 0 ? "CNN" : "Other";
692
+ }
693
+
694
+ function R(e) {
695
+ for (var n = {}, t = 0; t < e.length; t++) {
696
+ var r = e[t].group || N(e[t].name);
697
+ n[r] || (n[r] = []), n[r].push(e[t]);
698
+ }
699
+ return n;
700
+ }
701
+
702
+ function _() {
703
+ if (h) {
704
+ h.innerHTML = "";
705
+ for (var e = R(i), n = 0, t = Object.keys(e).sort(), r = 0; r < t.length; r++) {
706
+ var o = t[r], c = document.createElement("div");
707
+ c.className = "grp", c.textContent = o, h.appendChild(c);
708
+ for (var u = 0; u < e[o].length; u++) {
709
+ var s = e[o][u], l = document.createElement("button");
710
+ l.className = "ch", l.textContent = n + 1 + ". " + s.name, l.setAttribute("data-idx", String(s.index)), l.onclick = j(s.index), l.onfocus = q(s.index), h.appendChild(l), n++;
711
+ }
712
+ }
713
+ var menuItem = document.createElement("button");
714
+ menuItem.className = "ch menu-item", menuItem.textContent = "Change playlist", menuItem.setAttribute("data-idx", String(i.length)), menuItem.onclick = resetPlaylist, menuItem.onfocus = function() { p = i.length; D(i.length); }, h.appendChild(menuItem);
715
+ var m = document.getElementById("cnt");
716
+ m && (m.textContent = i.length + " channels"), x();
717
+ }
718
+ }
719
+
720
+ function j(e) { return function() { y(e); }; }
721
+ function q(e) { return function() { p = e; D(e); }; }
722
+
723
+ function D(e) {
724
+ for (var n = document.querySelectorAll(".ch"), t = 0; t < n.length; t++) {
725
+ var r = n[t], o = parseInt(r.getAttribute("data-idx") || "-1", 10);
726
+ o === e ? (r.className = "ch on", r.scrollIntoView(!1)) : r.className = "ch";
727
+ }
728
+ }
729
+
730
+ function L(e) {
731
+ if (i.length !== 0) {
732
+ var n = p + e;
733
+ if (n >= i.length) {
734
+ if (p === i.length) { n = 0; }
735
+ else {
736
+ var menuEl = document.querySelector(".menu-item");
737
+ if (menuEl) { p = i.length; D(p); menuEl.focus(); g(); return; }
738
+ n = 0;
739
+ }
740
+ } else if (n < 0) {
741
+ n = i.length - 1;
742
+ }
743
+ y(n); x();
744
+ }
745
+ }
746
+
747
+ function x() {
748
+ for (var e = document.querySelectorAll(".ch"), n = 0; n < e.length; n++) {
749
+ var t = parseInt(e[n].getAttribute("data-idx") || "-1", 10);
750
+ if (t === p) { e[n].focus(); return; }
751
+ }
752
+ e.length > 0 && e[0].focus();
753
+ }
754
+
755
+ // ===== MODIFIED: Play/Pause with DASH support =====
756
+ function X() {
757
+ if (isDashStream && dashPlayer) {
758
+ try {
759
+ if (dashPlayer.isPaused()) {
760
+ dashPlayer.play();
761
+ } else {
762
+ dashPlayer.pause();
763
+ }
764
+ } catch (e) {}
765
+ } else if (a) {
766
+ if (a.paused) {
767
+ var e = a.play();
768
+ e && e.catch && e.catch(function() {});
769
+ } else { a.pause(); }
770
+ }
771
+ }
772
+
773
+ // ===== MODIFIED: Seek with DASH support =====
774
+ function P(e) {
775
+ if (isDashStream && dashPlayer) {
776
+ try {
777
+ var newTime = Math.max(0, dashPlayer.time() + e);
778
+ dashPlayer.seek(newTime);
779
+ } catch (err) {}
780
+ } else if (a) {
781
+ a.currentTime = Math.max(0, a.currentTime + e);
782
+ }
783
+ }
784
+
785
+ function A(e) { a && (a.volume = Math.max(0, Math.min(1, a.volume + e))); }
786
+
787
+ function Y() {
788
+ if (v) {
789
+ var e = parseInt(v, 10);
790
+ v = "", e >= 1 && e <= i.length && (y(e - 1), x());
791
+ }
792
+ }
793
+
794
+ // ===== MODIFIED: Reset playlist with DASH cleanup =====
795
+ function resetPlaylist() {
796
+ stopDash();
797
+ try { localStorage.removeItem(LS); } catch (r) {}
798
+ I = "";
799
+ showSetup();
800
+ }
801
+
802
+ function showSetup() {
803
+ var code = makeSetupCode();
804
+ setupCode = code;
805
+ document.body.innerHTML = '<div id="app" style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;background:#1a1a1a;color:#fff;font-family:-apple-system,BlinkMacSystemFont,\'Segoe UI\',sans-serif;padding:20px"><h1 style="font-size:28px;font-weight:600;margin-bottom:8px">IPTV Player</h1><p style="color:#888;margin-bottom:20px">Scan this QR code with your phone to submit your M3U playlist URL.</p><div id="qr" style="background:#fff;padding:8px;border-radius:8px;margin-bottom:16px"></div><div id="setup-url" style="color:#aaa;font-size:12px;word-break:break-all;max-width:360px;text-align:center;margin-bottom:8px">' + setupUrl(code) + '</div><div id="setup-code" style="color:#ffd600;font-size:20px;font-weight:700;margin-bottom:8px">Code: ' + code + '</div><div id="st" style="color:#ffd600">Scan QR to set playlist URL</div></div>';
806
+ var qrDiv = document.getElementById("qr");
807
+ new QRCode(qrDiv, { text: setupUrl(code), width: 220, height: 220 });
808
+ startSetupPolling();
809
+ }
810
+
811
+ function startSetupPolling() {
812
+ clearSetupTimer();
813
+ setupTimer = setInterval(function() {
814
+ xhrGetText(W + "/api/config?code=" + encodeURIComponent(setupCode), function(err, status, text) {
815
+ if (err) { d("Setup poll failed: " + (err.message || "unknown")); return; }
816
+ if (status === 404) return;
817
+ if (status < 200 || status >= 300) { d("Setup poll failed: HTTP " + status); return; }
818
+ try {
819
+ var data = JSON.parse(text);
820
+ if (data && typeof data.playlistUrl === "string" && data.playlistUrl) {
821
+ clearSetupTimer();
822
+ savePlaylistUrl(data.playlistUrl);
823
+ U();
824
+ K();
825
+ }
826
+ } catch (parseErr) {
827
+ d("Setup poll failed: bad response");
828
+ }
829
+ });
830
+ }, 3000);
831
+ }
832
+
833
+ // ===== MODIFIED: MediaStop with DASH cleanup =====
834
+ function G(e) {
835
+ g();
836
+ var n = e.key && e.key !== "Unidentified" ? e.key : B[e.keyCode];
837
+ switch (n) {
838
+ case "ArrowUp":
839
+ case "ChannelUp":
840
+ e.preventDefault(), L(-1);
841
+ break;
842
+ case "ArrowDown":
843
+ case "ChannelDown":
844
+ e.preventDefault(), L(1);
845
+ break;
846
+ case "ArrowLeft":
847
+ e.preventDefault(), P(-10);
848
+ break;
849
+ case "ArrowRight":
850
+ e.preventDefault(), P(10);
851
+ break;
852
+ case "Enter":
853
+ e.preventDefault();
854
+ if (p === i.length) { resetPlaylist(); } else { y(p); }
855
+ break;
856
+ case " ":
857
+ case "MediaPlayPause":
858
+ case "MediaPlay":
859
+ case "MediaPause":
860
+ e.preventDefault(), X();
861
+ break;
862
+ case "VolumeUp":
863
+ e.preventDefault(), A(0.1);
864
+ break;
865
+ case "VolumeDown":
866
+ e.preventDefault(), A(-0.1);
867
+ break;
868
+ case "Backspace":
869
+ case "Escape":
870
+ e.preventDefault(), x();
871
+ break;
872
+ case "ColorF2Yellow":
873
+ e.preventDefault(), resetPlaylist();
874
+ break;
875
+ case "MediaStop":
876
+ e.preventDefault();
877
+ stopDash();
878
+ if (a) { a.pause(); a.src = ""; }
879
+ break;
880
+ default:
881
+ n && n >= "0" && n <= "9" && (e.preventDefault(), v += n, w && clearTimeout(w), w = setTimeout(Y, 1e3));
882
+ break;
883
+ }
884
+ }
885
+
886
+ function J() {
887
+ try {
888
+ var e = window.tizen && window.tizen.tvinputdevice;
889
+ if (!e) return;
890
+ for (var n = ["MediaPlay", "MediaPause", "MediaStop", "ChannelUp", "ChannelDown", "ColorF2Yellow"], t = 0; t < n.length; t++) e.registerKey(n[t]);
891
+ } catch (r) {}
892
+ }
893
+
894
+ // ===== MODIFIED: Main init with DASH loading =====
895
+ function O() {
896
+ U();
897
+ J();
898
+ document.addEventListener("keydown", G);
899
+ document.addEventListener("click", g);
900
+ document.addEventListener("mousemove", g);
901
+ try { I = localStorage.getItem(LS) || ""; } catch (err) { I = ""; }
902
+ if (I) K(); else showSetup();
903
+ }
904
+
905
+ // ===== ADDED: Load dash.js dynamically =====
906
+ function loadDashAndStart() {
907
+ if (typeof dashjs !== 'undefined') {
908
+ // dash.js already loaded
909
+ if (document.readyState === "loading") {
910
+ document.addEventListener("DOMContentLoaded", O);
911
+ } else {
912
+ O();
913
+ }
914
+ } else {
915
+ // Load dash.js from CDN
916
+ var script = document.createElement('script');
917
+ script.src = 'https://cdn.dashjs.org/latest/dash.all.min.js';
918
+ script.onload = function() {
919
+ if (document.readyState === "loading") {
920
+ document.addEventListener("DOMContentLoaded", O);
921
+ } else {
922
+ O();
923
+ }
924
+ };
925
+ script.onerror = function() {
926
+ console.warn('dash.js failed to load. DASH streams will not work.');
927
+ if (document.readyState === "loading") {
928
+ document.addEventListener("DOMContentLoaded", O);
929
+ } else {
930
+ O();
931
+ }
932
+ };
933
+ document.head.appendChild(script);
934
+ }
935
+ }
936
+
937
+ // Start the app with DASH support
938
+ loadDashAndStart();
939
+ })();
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "name": "tizenbrew-iptv",
4
+ "displayName": "IPTV Player",
5
+ "version": "0.1.9",
6
+ "description": "IPTV Player for TizenBrew with QR playlist setup",
7
+ "targetUrl": "https://localhost",
8
+ "assets": {
9
+ "scripts": [
10
+ "inject.js"
11
+ ],
12
+ "styles": []
13
+ },
14
+ "capabilities": {
15
+ "tvKeys": {
16
+ "arrows": true,
17
+ "enter": true,
18
+ "back": true,
19
+ "playPause": true
20
+ },
21
+ "performance": {
22
+ "removeAnimations": false,
23
+ "lazyMedia": false,
24
+ "hideComments": false,
25
+ "memorySaver": false
26
+ }
27
+ }
28
+ }
package/index.html ADDED
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>IPTV Player</title>
7
+ </head>
8
+ <body>
9
+ <div id="status-text">Loading IPTV Player...</div>
10
+ <script>
11
+ window.onerror = function (message, source, line) {
12
+ var status = document.getElementById('status-text');
13
+ if (status) status.textContent = 'Startup error: ' + message + ' (line ' + line + ')';
14
+ };
15
+ </script>
16
+ <script src="./dist/inject.js"></script>
17
+ </body>
18
+ </html>
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "tizenos-iptv",
3
+ "version": "0.2.2",
4
+ "description": "IPTV Player for TizenBrew w/ QR",
5
+ "license": "MIT",
6
+ "private": false,
7
+ "packageType": "app",
8
+ "appName": "IPTV Player",
9
+ "appPath": "index.html",
10
+ "keys": [],
11
+ "files": [
12
+ "index.html",
13
+ "dist"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "scripts": {
19
+ "pack:check": "npm pack --dry-run",
20
+ "publish:public": "npm publish --access public"
21
+ },
22
+ "keywords": [
23
+ "iptv",
24
+ "tizen",
25
+ "tizenbrew",
26
+ "tv",
27
+ "player",
28
+ "m3u",
29
+ "streaming",
30
+ "dash"
31
+ ]
32
+ }