@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/.github/FUNDING.yml +2 -1
- package/.github/assets/profiles.xml +18 -0
- package/.github/scripts/build-release-notes.sh +113 -0
- package/.github/workflows/build-publish-cleanup.yml +674 -0
- package/.github/workflows/build-standalone-release.yaml +305 -0
- package/.github/workflows/claude.yml +58 -0
- package/.github/workflows/codeql.yml +44 -0
- package/.github/workflows/upstream-sync.yml +165 -0
- package/AGENTS.md +898 -0
- package/README.md +202 -12
- package/dist/service.js +15267 -15966
- package/dist/userScript.js +3 -13
- package/package.json +4 -5
- package/scripts/log-receiver/receiver.ps1 +116 -0
- package/scripts/tampermonkey/tizentube-loader.user.js +18 -0
- package/scripts/tampermonkey/tizentube-log-button.user.js +54 -0
- package/standalone/config.xml +41 -0
- package/standalone/icon.png +0 -0
- package/standalone/icon_16b9.png +0 -0
- package/standalone/index.html +240 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krx3d/tizentube2",
|
|
3
|
-
"appName": "
|
|
4
|
-
"version": "1.
|
|
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": "
|
|
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
|
-
"
|
|
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>
|