@krx3d/tizentube2 1.16.30 → 1.30.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.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@krx3d/tizentube2",
3
- "appName": "TizenTubeTest",
4
- "version": "1.16.30",
3
+ "appName": "TizenTube",
4
+ "version": "1.30.0",
5
5
  "description": "TizenTube is an ad-free and sponsor-free solution for your favourite streaming website on your Tizen (Samsung) TVs.",
6
6
  "packageType": "mods",
7
7
  "websiteURL": "https://youtube.com/tv?additionalDataUrl=http%3A%2F%2Flocalhost%3A8085%2Fdial%2Fapps%2FYouTube",
8
8
  "main": "dist/userScript.js",
9
- "author": "KrX3D",
9
+ "author": "Reis Can",
10
10
  "serviceFile": "dist/service.js",
11
11
  "keys": [
12
12
  "MediaPlayPause",
@@ -19,8 +19,7 @@
19
19
  "MediaTrackPrevious"
20
20
  ],
21
21
  "repository": {
22
- "type": "git",
23
- "url": "git+https://github.com/KrX3D/TizenTube.git"
22
+ "url": "https://github.com/KrX3D/TizenTube"
24
23
  },
25
24
  "license": "GPL-3.0-only"
26
25
  }
@@ -0,0 +1,116 @@
1
+ param(
2
+ [int]$Port = 3030,
3
+ [string]$OutputFile = "tv.log",
4
+ [string]$SyslogForwardIp = "", # Set to your syslog server IP to forward (e.g. "192.168.1.10")
5
+ [int]$SyslogForwardPort = 514
6
+ )
7
+
8
+ $ErrorActionPreference = 'Stop'
9
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
10
+ $LogPath = Join-Path $ScriptDir $OutputFile
11
+
12
+ $Listener = [System.Net.HttpListener]::new()
13
+ $Listener.Prefixes.Add("http://*:$Port/")
14
+ $Listener.Start()
15
+
16
+ Write-Host "Log receiver on http://*:$Port/tv-log → $LogPath"
17
+ if ($SyslogForwardIp) {
18
+ Write-Host "Syslog forwarding: UDP $SyslogForwardIp`:$SyslogForwardPort"
19
+ $UdpClient = [System.Net.Sockets.UdpClient]::new()
20
+ $UdpClient.Connect($SyslogForwardIp, $SyslogForwardPort)
21
+ } else {
22
+ Write-Host "Syslog forwarding: disabled (use -SyslogForwardIp to enable)"
23
+ $UdpClient = $null
24
+ }
25
+ Write-Host "Press Ctrl+C to stop."
26
+ Write-Host ""
27
+
28
+ function Format-LogEntry {
29
+ param($Parsed)
30
+ $lines = @()
31
+ $lines += ""
32
+ $lines += "─" * 72
33
+ if ($Parsed._formatted) {
34
+ # Use pre-formatted string from logger.js
35
+ $lines += $Parsed._formatted
36
+ } else {
37
+ # Fallback for plain JSON payloads
38
+ $ts = if ($Parsed.ts) { $Parsed.ts } else { (Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff") }
39
+ $lvl = if ($Parsed.level) { $Parsed.level } else { "INFO" }
40
+ $ctx = if ($Parsed.context) { $Parsed.context } else { "unknown" }
41
+ $msg = if ($Parsed.message) { $Parsed.message } else { ($Parsed | ConvertTo-Json -Compress) }
42
+ $lines += "[$ts] [$lvl] [$ctx] $msg"
43
+ if ($Parsed.data) {
44
+ $lines += " data: $($Parsed.data | ConvertTo-Json -Compress)"
45
+ }
46
+ }
47
+ return $lines -join "`n"
48
+ }
49
+
50
+ function Send-Syslog {
51
+ param($Parsed, $UdpClient)
52
+ if (-not $UdpClient) { return }
53
+ try {
54
+ $pri = 134 # facility=local0 (16), severity=info (6) → (16*8)+6 = 134
55
+ $ts = if ($Parsed.ts) { $Parsed.ts } else { (Get-Date -Format "yyyy-MM-dd HH:mm:ss") }
56
+ $host = "TizenTV"
57
+ $app = if ($Parsed.app) { $Parsed.app } else { "TizenYouTube" }
58
+ $msg = if ($Parsed.message) { $Parsed.message } else { ($Parsed | ConvertTo-Json -Compress) }
59
+ $syslog = "<$pri>1 $ts $host $app - - - $msg"
60
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($syslog)
61
+ $UdpClient.Send($bytes, $bytes.Length) | Out-Null
62
+ } catch {
63
+ Write-Host "Syslog forward error: $_" -ForegroundColor DarkYellow
64
+ }
65
+ }
66
+
67
+ try {
68
+ while ($Listener.IsListening) {
69
+ $Context = $Listener.GetContext()
70
+ $Request = $Context.Request
71
+ $Response = $Context.Response
72
+
73
+ $Response.Headers.Add("Access-Control-Allow-Origin", "*")
74
+ $Response.Headers.Add("Access-Control-Allow-Methods", "POST, OPTIONS")
75
+ $Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type")
76
+
77
+ if ($Request.HttpMethod -eq 'OPTIONS') {
78
+ $Response.StatusCode = 204
79
+ $Response.Close()
80
+ continue
81
+ }
82
+
83
+ if ($Request.HttpMethod -eq 'POST' -and $Request.Url.AbsolutePath -eq '/tv-log') {
84
+ $Reader = [System.IO.StreamReader]::new($Request.InputStream, $Request.ContentEncoding)
85
+ $Body = $Reader.ReadToEnd()
86
+ $Reader.Close()
87
+
88
+ $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
89
+ $Parsed = $null
90
+ try { $Parsed = $Body | ConvertFrom-Json }
91
+ catch { $Parsed = $null }
92
+
93
+ if ($Parsed) {
94
+ $Formatted = Format-LogEntry -Parsed $Parsed
95
+ Add-Content -Path $LogPath -Value $Formatted
96
+ Write-Host $Formatted -ForegroundColor Cyan
97
+ Send-Syslog -Parsed $Parsed -UdpClient $UdpClient
98
+ } else {
99
+ $line = "[$Timestamp] $Body"
100
+ Add-Content -Path $LogPath -Value $line
101
+ Write-Host $line
102
+ }
103
+
104
+ $Response.StatusCode = 204
105
+ } else {
106
+ $Response.StatusCode = 404
107
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes("Not Found")
108
+ $Response.OutputStream.Write($bytes, 0, $bytes.Length)
109
+ }
110
+ $Response.Close()
111
+ }
112
+ } finally {
113
+ $Listener.Stop()
114
+ $Listener.Close()
115
+ if ($UdpClient) { $UdpClient.Close() }
116
+ }
@@ -0,0 +1,18 @@
1
+ // ==UserScript==
2
+ // @name TizenTube Loader
3
+ // @namespace https://github.com/KrX3D/TizenTube
4
+ // @version 0.4
5
+ // @description Load TizenTube userscript bundle for local /tv debugging
6
+ // @match https://www.youtube.com/tv*
7
+ // @match https://youtube.com/tv*
8
+ // @run-at document-start
9
+ // @grant none
10
+ // @updateURL https://raw.githubusercontent.com/KrX3D/TizenTube/main/scripts/tampermonkey/tizentube-loader.user.js
11
+ // @downloadURL https://raw.githubusercontent.com/KrX3D/TizenTube/main/scripts/tampermonkey/tizentube-loader.user.js
12
+ // @require https://raw.githubusercontent.com/KrX3D/TizenTube/main/dist/userScript.js
13
+ // ==/UserScript==
14
+
15
+ (function () {
16
+ // The actual TizenTube code is loaded via @require.
17
+ // Tampermonkey updates this script by @version and update checks.
18
+ })();
@@ -0,0 +1,54 @@
1
+ // ==UserScript==
2
+ // @name TizenTube Log Download Button
3
+ // @namespace https://github.com/KrX3D/TizenTube
4
+ // @version 0.4
5
+ // @description Inject a floating button to trigger window.downloadTizenTubeLogs()
6
+ // @match https://www.youtube.com/tv*
7
+ // @match https://youtube.com/tv*
8
+ // @run-at document-idle
9
+ // @grant none
10
+ // @updateURL https://raw.githubusercontent.com/KrX3D/TizenTube/main/scripts/tampermonkey/tizentube-log-button.user.js
11
+ // @downloadURL https://raw.githubusercontent.com/KrX3D/TizenTube/main/scripts/tampermonkey/tizentube-log-button.user.js
12
+ // ==/UserScript==
13
+
14
+ (function () {
15
+ const buttonId = 'tt-log-download-btn';
16
+
17
+ const ensureButton = () => {
18
+ if (document.getElementById(buttonId)) return;
19
+
20
+ const btn = document.createElement('button');
21
+ btn.id = buttonId;
22
+ btn.textContent = 'TT Logs';
23
+ btn.style.cssText = [
24
+ 'position:fixed',
25
+ 'right:14px',
26
+ 'bottom:14px',
27
+ 'z-index:2147483647',
28
+ 'padding:8px 10px',
29
+ 'font-size:12px',
30
+ 'font-family:monospace',
31
+ 'color:#0f0',
32
+ 'background:#111',
33
+ 'border:1px solid #0f0',
34
+ 'border-radius:4px',
35
+ 'cursor:pointer',
36
+ 'opacity:0.9'
37
+ ].join(';');
38
+
39
+ btn.addEventListener('click', () => {
40
+ if (typeof window.downloadTizenTubeLogs === 'function') {
41
+ window.downloadTizenTubeLogs();
42
+ } else {
43
+ console.warn('[TT_LOG_BUTTON] window.downloadTizenTubeLogs is not available yet');
44
+ }
45
+ });
46
+
47
+ document.body.appendChild(btn);
48
+ };
49
+
50
+ const interval = setInterval(() => {
51
+ if (document.body) ensureButton();
52
+ if (document.getElementById(buttonId)) clearInterval(interval);
53
+ }, 500);
54
+ })();
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <widget xmlns:tizen="http://tizen.org/ns/widgets" xmlns="http://www.w3.org/ns/widgets" id="https://tizentube.app" version="1.0.0" viewmodes="maximized">
3
+ <access origin="*" subdomains="true"></access>
4
+ <tizen:allow-navigation href="*"/>
5
+ <tizen:app-control>
6
+ <tizen:src name="index.html" reload="disable"/>
7
+ <tizen:operation name="http://samsung.com/appcontrol/operation/eden_resume"/>
8
+ </tizen:app-control>
9
+ <tizen:application id="krx3dTtSt1.TizenTubeStandalone" package="krx3dTtSt1" required_version="3.0"/>
10
+ <author href="https://github.com/KrX3D/TizenTube">KrX3D</author>
11
+ <content src="index.html"/>
12
+ <feature name="http://tizen.org/feature/screen.size.all"/>
13
+ <feature name="http://tizen.org/feature/web.service"/>
14
+ <icon src="icon_16b9.png" width="1920" height="1080"/>
15
+ <icon src="icon.png" width="1024" height="1024"/>
16
+ <tizen:metadata key="http://samsung.com/tv/metadata/prelaunch.support" value="true"/>
17
+ <tizen:metadata key="http://samsung.com/tv/metadata/floating.navigation" value="false"/>
18
+ <tizen:metadata key="http://samsung.com/tv/metadata/use.game.mode" value="true"/>
19
+ <tizen:metadata key="http://samsung.com/tv/metadata/use.uwe" value="true"/>
20
+ <tizen:metadata key="http://samsung.com/tv/metadata/no_use_process_pool" value="true"/>
21
+ <tizen:metadata key="multiview_support" value="true"/>
22
+ <tizen:metadata key="multiview_min_resource" value="FHD+"/>
23
+ <tizen:metadata key="http://samsung.com/tv/metadata/html5.4k.support" value="true"/>
24
+ <tizen:metadata key="http://samsung.com/tv/metadata/microphone.support" value="1"/>
25
+ <tizen:metadata key="http://samsung.com/tv/metadata/microphone.area" value="open"/>
26
+ <name>TizenTube</name>
27
+ <tizen:privilege name="http://tizen.org/privilege/internet"/>
28
+ <tizen:privilege name="http://developer.samsung.com/privilege/network.public"/>
29
+ <tizen:privilege name="http://tizen.org/privilege/tv.inputdevice"/>
30
+ <tizen:privilege name="http://tizen.org/privilege/recorder"/>
31
+ <tizen:privilege name="http://tizen.org/privilege/mediacapture"/>
32
+ <tizen:privilege name="http://tizen.org/privilege/unlimitedstorage"/>
33
+ <tizen:privilege name="http://tizen.org/privilege/application.launch"/>
34
+ <tizen:profile name="tv-samsung"/>
35
+ <tizen:service id="krx3dTtSt1.StandaloneService">
36
+ <tizen:content src="service/dist/index.js"/>
37
+ <tizen:name>StandaloneService</tizen:name>
38
+ <tizen:description>StandaloneService</tizen:description>
39
+ </tizen:service>
40
+ <tizen:setting screen-orientation="landscape" context-menu="enable" background-support="enable" encryption="disable" install-location="auto" hwkey-event="enable"/>
41
+ </widget>
Binary file
Binary file
@@ -0,0 +1,240 @@
1
+ <!DOCTYPE html>
2
+
3
+ <head>
4
+ <style>
5
+ @keyframes indeterminate-bar {
6
+ 0% {
7
+ left: 0%;
8
+ width: 0;
9
+ }
10
+
11
+ 50% {
12
+ left: 0%;
13
+ width: 100%;
14
+ }
15
+
16
+ 100% {
17
+ left: 100%;
18
+ width: 0;
19
+ }
20
+ }
21
+
22
+ .progress-container {
23
+ position: relative;
24
+ width: 300px;
25
+ height: 4px;
26
+ margin-top: 40px;
27
+ background-color: rgba(25, 118, 210, 0.3);
28
+ border-radius: 2px;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .progress-bar {
33
+ position: absolute;
34
+ top: 0;
35
+ left: 0;
36
+ height: 100%;
37
+ background-color: #1976D2;
38
+ animation: indeterminate-bar 1.5s cubic-bezier(0.4, 0, 0.2, 1) infinite;
39
+ }
40
+ </style>
41
+ <script>
42
+ "use strict";
43
+
44
+ // Earliest possible diagnostic hook — runs before the service is even
45
+ // launched, so it can catch failures the service's own self-logging
46
+ // never gets a chance to (e.g. launchAppControl's callback never
47
+ // firing at all). Same /tv-log target and payload shape as the
48
+ // service's own relayLog, so scripts/log-receiver/receiver.ps1 needs
49
+ // no changes.
50
+ // No address is written down here any more. index.html cannot read the
51
+ // page's settings — those live in localStorage on a different origin —
52
+ // so instead of carrying a constant it hands its lines to the service,
53
+ // which knows the receiver the user configured in the settings menu and
54
+ // persists it across restarts.
55
+ //
56
+ // The service is not listening yet when the first lines are produced, so
57
+ // they are buffered and flushed as soon as it answers. Bounded, because
58
+ // a service that never comes up must not grow this forever.
59
+ var MAX_BUFFERED_LOGS = 100;
60
+ var bufferedLogs = [];
61
+
62
+ function postLog(entry) {
63
+ return fetch('http://localhost:8099/tizentube/log', {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json' },
66
+ body: JSON.stringify({ entry: entry })
67
+ });
68
+ }
69
+
70
+ function flushBufferedLogs() {
71
+ if (!bufferedLogs.length) return;
72
+ var pending = bufferedLogs.splice(0, bufferedLogs.length);
73
+ for (var i = 0; i < pending.length; i++) {
74
+ (function (entry) {
75
+ try {
76
+ postLog(entry).catch(function () { });
77
+ } catch (e) { }
78
+ })(pending[i]);
79
+ }
80
+ }
81
+
82
+ function sendLog(level, message) {
83
+ var ts = new Date().toISOString();
84
+ var entry = {
85
+ _formatted: '[' + ts + '] [' + level + '] [StandaloneIndexHtml] ' + message,
86
+ app: 'TizenTube Standalone',
87
+ ts: ts,
88
+ level: level,
89
+ context: 'StandaloneIndexHtml',
90
+ message: message
91
+ };
92
+ try {
93
+ postLog(entry).then(function () {
94
+ flushBufferedLogs();
95
+ }).catch(function () {
96
+ if (bufferedLogs.length < MAX_BUFFERED_LOGS) bufferedLogs.push(entry);
97
+ });
98
+ } catch (e) {
99
+ if (bufferedLogs.length < MAX_BUFFERED_LOGS) bufferedLogs.push(entry);
100
+ }
101
+ }
102
+
103
+ sendLog('INFO', 'index.html script started');
104
+
105
+ tizen.tvinputdevice.registerKey("ColorF0Red");
106
+ tizen.tvinputdevice.registerKey("ColorF1Green");
107
+ tizen.tvinputdevice.registerKey("ColorF2Yellow");
108
+ tizen.tvinputdevice.registerKey("ColorF3Blue");
109
+
110
+ const keys = [
111
+ "MediaPlayPause",
112
+ "MediaPlay",
113
+ "MediaPause",
114
+ "MediaStop",
115
+ "MediaFastForward",
116
+ "MediaRewind",
117
+ "MediaTrackNext",
118
+ "MediaTrackPrevious"
119
+ ];
120
+
121
+ for (let i = 0; i < keys.length; i++) {
122
+ tizen.tvinputdevice.registerKey(keys[i]);
123
+ }
124
+
125
+ const pkgId = tizen.application.getCurrentApplication().appInfo.packageId;
126
+
127
+ const serviceId = pkgId + ".StandaloneService";
128
+
129
+ // Bounds the isConnecting poll below. Waiting is preferred over the proxy
130
+ // path — the proxy rewrites YouTube's traffic, which is confirmed to
131
+ // trigger verification challenges that stop playback (see README) — but
132
+ // waiting forever is how a wedged isConnecting became a permanent splash
133
+ // screen. Roughly 30s of patience, then take the degraded path rather
134
+ // than none at all.
135
+ var MAX_CONNECTING_POLLS = 30;
136
+ var connectingPolls = 0;
137
+
138
+ // The service needs roughly three seconds to listen; these cover that
139
+ // without restarting the page underneath it.
140
+ var MAX_STARTUP_RETRIES = 20;
141
+ var STARTUP_RETRY_MS = 300;
142
+ var startupRetries = 0;
143
+
144
+ function useInjectorOrProxy() {
145
+ sendLog('INFO', 'useInjectorOrProxy() called, fetching getState');
146
+ const data = tizen.application.getCurrentApplication().getRequestedAppControl().appControl.data;
147
+ let args;
148
+ try {
149
+ const parsedData = JSON.parse(data[0].value[0]);
150
+ args = parsedData.args;
151
+ } catch (e) {
152
+ // Do nothing.
153
+ }
154
+ fetch('http://localhost:8099/tizentube/getState')
155
+ .then(r => r.json())
156
+ .then(res => {
157
+ sendLog('INFO', 'getState resolved: ' + JSON.stringify(res));
158
+ if (res.canConnectToDaemon && !res.isConnecting) {
159
+ sendLog('INFO', 'Taking CDP-injection path, launching debugger and exiting');
160
+ fetch(`http://localhost:8099/tizentube/debugger${args ? `?${args}` : ''}`);
161
+ tizen.application.getCurrentApplication().exit();
162
+ } else if (!res.canConnectToDaemon) {
163
+ sendLog('INFO', 'Taking proxy path, navigating to localhost:8099/tv');
164
+ location.href = `http://localhost:8099/tv?additionalDataUrl=http%3A%2F%2Flocalhost%3A8095%2Fdial%2Fapps%2FYouTube${args ? `&${args}` : ''}`
165
+ } else if (connectingPolls < MAX_CONNECTING_POLLS) {
166
+ // canConnectToDaemon && isConnecting: a connection attempt is
167
+ // (or was) already in progress. Neither branch above matched,
168
+ // so previously this silently did nothing — a permanent hang
169
+ // whenever isConnecting got stuck true (e.g. a previous CDP
170
+ // attempt that never completed or failed, confirmed on-device).
171
+ // Poll rather than give up: injector.js times out a stuck
172
+ // attempt, so the usual outcome is the preferred CDP path.
173
+ connectingPolls++;
174
+ sendLog('INFO', 'canConnectToDaemon but isConnecting, retrying in 1s (' + connectingPolls + '/' + MAX_CONNECTING_POLLS + ')');
175
+ setTimeout(useInjectorOrProxy, 1000);
176
+ } else {
177
+ // Out of patience. isConnecting has stayed true well past
178
+ // injector.js's own give-up point, so nothing is coming —
179
+ // fall back to the proxy instead of sitting on the splash
180
+ // screen indefinitely, which is what this state used to do.
181
+ sendLog('ERROR', 'isConnecting still true after ' + MAX_CONNECTING_POLLS + ' polls, falling back to proxy path');
182
+ location.href = `http://localhost:8099/tv?additionalDataUrl=http%3A%2F%2Flocalhost%3A8095%2Fdial%2Fapps%2FYouTube${args ? `&${args}` : ''}`
183
+ }
184
+ }).catch((err) => {
185
+ // The service takes a couple of seconds to come up, and every
186
+ // getState during that window fails. Reloading the whole page
187
+ // for each one restarted launchAppControl too, producing 16
188
+ // reloads in 3 seconds on a 6.5 capture — wasted work that
189
+ // shows up as a long wait on the loading screen. Retry just
190
+ // the fetch instead, and only fall back to a reload once it
191
+ // is clear the service is not merely slow to start.
192
+ startupRetries++;
193
+ if (startupRetries <= MAX_STARTUP_RETRIES) {
194
+ sendLog('INFO', 'getState not up yet, retrying in ' + STARTUP_RETRY_MS + 'ms (' + startupRetries + '/' + MAX_STARTUP_RETRIES + ')');
195
+ setTimeout(useInjectorOrProxy, STARTUP_RETRY_MS);
196
+ return;
197
+ }
198
+ sendLog('ERROR', 'getState still failing after ' + MAX_STARTUP_RETRIES + ' retries, reloading: ' + (err && err.message || err));
199
+ window.location.reload();
200
+ });
201
+ }
202
+
203
+ function launchService() {
204
+ sendLog('INFO', 'Launching StandaloneService via launchAppControl');
205
+ tizen.application.launchAppControl(
206
+ new tizen.ApplicationControl("http://tizen.org/appcontrol/operation/service"),
207
+ serviceId,
208
+ function () {
209
+ sendLog('INFO', 'launchAppControl success callback fired');
210
+ useInjectorOrProxy();
211
+ },
212
+ function (e) {
213
+ // Confirmed on-device: right after a TV reboot, this can
214
+ // genuinely fail (Tizen's own service-launch subsystem
215
+ // still warming up) — previously just showed an alert
216
+ // and stopped, unlike the getState-fetch-failure path
217
+ // below which already retries automatically. User had
218
+ // to manually close/reopen the app to get past it.
219
+ // Retry the same way instead.
220
+ sendLog('ERROR', 'launchAppControl failed, retrying in 1s: ' + e.message);
221
+ setTimeout(launchService, 1000);
222
+ }
223
+ );
224
+ }
225
+ launchService();
226
+ </script>
227
+ </head>
228
+
229
+ <body
230
+ style="background-color: #0f0f0f; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; margin: 0;">
231
+ <h1
232
+ style="color: #FFFFFF; font-size: 80px; font-family: Roboto,Noto Sans CJK ZH-HK,Noto Sans Thai,Arial,Helvetica,sans-serif; margin: 0;">
233
+ TizenTube
234
+ </h1>
235
+ <div class="progress-container">
236
+ <div class="progress-bar"></div>
237
+ </div>
238
+ </body>
239
+
240
+ </html>