@oxymoron.13/tizentube-s95b 0.3.0 → 0.3.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 +3 -1
- package/S95B-CHANGES.txt +4 -3
- package/dist/userScript.js +2 -2
- package/mods/features/pictureInPicture.js +3 -6
- package/mods/features/s95bI18n.js +26 -0
- package/mods/features/s95bMenu.js +61 -3
- package/mods/features/s95bMiniPlayer.js +36 -0
- package/mods/features/s95bVersion.js +2 -0
- package/mods/resolveCommand.js +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Picture in Picture Mode for TizenTube
|
|
2
2
|
|
|
3
3
|
import resolveCommand from "../resolveCommand.js";
|
|
4
|
+
import { s95bMiniLayout, s95bMiniEntered } from './s95bMiniPlayer.js';
|
|
4
5
|
|
|
5
6
|
window.isPipPlaying = false;
|
|
6
7
|
let PlayerService = null;
|
|
@@ -51,14 +52,10 @@ function enablePip() {
|
|
|
51
52
|
|
|
52
53
|
function onPipEnter() {
|
|
53
54
|
videoElement.style.removeProperty('inset');
|
|
54
|
-
|
|
55
|
-
const pipHeight = window.innerHeight / 3.5;
|
|
56
|
-
videoElement.style.width = `${pipWidth}px`;
|
|
57
|
-
videoElement.style.height = `${pipHeight}px`;
|
|
58
|
-
videoElement.style.top = '68vh';
|
|
59
|
-
videoElement.style.left = '68vw';
|
|
55
|
+
s95bMiniLayout(videoElement);
|
|
60
56
|
|
|
61
57
|
window.isPipPlaying = true;
|
|
58
|
+
s95bMiniEntered(videoElement);
|
|
62
59
|
videoElement.removeEventListener('play', onPipEnter);
|
|
63
60
|
}
|
|
64
61
|
|
|
@@ -10,6 +10,19 @@ export const STRINGS = Object.freeze({
|
|
|
10
10
|
statVideo: 'Video', statFrames: 'Verlorene Bilder', statCodec: 'Codec-Wunsch',
|
|
11
11
|
statUnavailable: 'nicht verfügbar', statWait: 'wartet auf Video',
|
|
12
12
|
languageDE: 'Deutsch', languageEN: 'English',
|
|
13
|
+
exit: 'Beenden',
|
|
14
|
+
exitHintTizen: 'Beendet die App (TizenTube und TizenBrew, wie die Zurück-Taste in TizenBrew). TizenBrews Hintergrunddienst wird dabei nicht gestoppt.',
|
|
15
|
+
exitHintSignal: 'Die Tizen-Funktion zum Beenden ist hier nicht verfügbar; es wird YouTubes Beenden-Signal versucht (am TV ungeprüft). Sonst die Home-Taste der Fernbedienung benutzen.',
|
|
16
|
+
exitCancel: 'Abbrechen', exitNow: 'Ja, App beenden',
|
|
17
|
+
exitFailed: 'Beenden nicht möglich. Bitte die Home-Taste der Fernbedienung benutzen.',
|
|
18
|
+
mini: 'Mini-Player',
|
|
19
|
+
miniHint: 'TizenTubes Mini-Player (Eintrag „MiniPlayer“ im Video-Menü). Änderungen gelten beim nächsten Start des Mini-Players. Ein neu gestartetes Video öffnet im Vollbild und ersetzt den Mini-Player.',
|
|
20
|
+
miniOn: 'Mini-Player anbieten',
|
|
21
|
+
miniOnHint: 'Eintrag im Video-Menü und Schaltfläche in der Player-Leiste (die Leiste ändert sich eventuell erst nach einem Neustart von YouTube).',
|
|
22
|
+
miniPos: 'Position', posBR: 'Unten rechts', posBL: 'Unten links', posTR: 'Oben rechts', posTL: 'Oben links',
|
|
23
|
+
miniSize: 'Größe', sizeSmall: 'Klein', sizeMedium: 'Mittel (wie bisher)', sizeLarge: 'Groß',
|
|
24
|
+
miniBrowse: 'Beim Stöbern', browsePlay: 'Weiterlaufen', browsePause: 'Pausieren',
|
|
25
|
+
miniBrowseHint: 'Pausieren: Das Video hält im kleinen Fenster an (der Anfang ist kurz hörbar) und läuft im Vollbild an derselben Stelle weiter.',
|
|
13
26
|
}),
|
|
14
27
|
en: Object.freeze({
|
|
15
28
|
title: 'S95B · Performance', language: 'Language', codec: 'Video codec', quality: 'Default quality (all videos)',
|
|
@@ -21,6 +34,19 @@ export const STRINGS = Object.freeze({
|
|
|
21
34
|
statVideo: 'Video', statFrames: 'Dropped frames', statCodec: 'Codec preference',
|
|
22
35
|
statUnavailable: 'not available', statWait: 'waiting for video',
|
|
23
36
|
languageDE: 'Deutsch', languageEN: 'English',
|
|
37
|
+
exit: 'Exit',
|
|
38
|
+
exitHintTizen: 'Closes the app (TizenTube and TizenBrew, like the Back key in TizenBrew). TizenBrew’s background service is not stopped.',
|
|
39
|
+
exitHintSignal: 'The Tizen exit function is not available here; YouTube’s exit signal is tried instead (untested on the TV). Otherwise use the remote’s Home key.',
|
|
40
|
+
exitCancel: 'Cancel', exitNow: 'Yes, exit the app',
|
|
41
|
+
exitFailed: 'Could not exit. Please use the remote’s Home key.',
|
|
42
|
+
mini: 'Mini player',
|
|
43
|
+
miniHint: 'TizenTube’s mini player (“MiniPlayer” row in the video menu). Changes apply the next time the mini player starts. A newly started video opens full screen and replaces the mini player.',
|
|
44
|
+
miniOn: 'Offer mini player',
|
|
45
|
+
miniOnHint: 'Row in the video menu and button in the player bar (the bar may only change after restarting YouTube).',
|
|
46
|
+
miniPos: 'Position', posBR: 'Bottom right', posBL: 'Bottom left', posTR: 'Top right', posTL: 'Top left',
|
|
47
|
+
miniSize: 'Size', sizeSmall: 'Small', sizeMedium: 'Medium (as before)', sizeLarge: 'Large',
|
|
48
|
+
miniBrowse: 'While browsing', browsePlay: 'Keep playing', browsePause: 'Pause',
|
|
49
|
+
miniBrowseHint: 'Pause: the video stops in the small window (its start is briefly audible) and continues full screen from the same position.',
|
|
24
50
|
})
|
|
25
51
|
});
|
|
26
52
|
export function translate(lang, key) {
|
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { configRead, configWrite, configChangeEmitter } from '../config.js';
|
|
7
7
|
import { optionShow } from '../ui/settings.js';
|
|
8
|
+
import { showToast } from '../ui/ytUI.js';
|
|
9
|
+
import resolveCommand from '../resolveCommand.js';
|
|
10
|
+
import { S95B_VERSION_TAG } from './s95bVersion.js';
|
|
8
11
|
import { translate } from './s95bI18n.js';
|
|
9
12
|
import { codecMenu, qualityMenu, codecLabel, currentCodec } from './s95bOptions.js';
|
|
13
|
+
import { MINI_POSITIONS, MINI_SIZES, MINI_BROWSE } from './s95bMiniPlayer.js';
|
|
10
14
|
|
|
11
15
|
const STATE_KEY = 's95b-perf-edition-v1';
|
|
12
16
|
const EDITED = ['enablePreviews', 'enableDeArrow', 'enableDeArrowThumbnails'];
|
|
17
|
+
const VERSION = String(S95B_VERSION_TAG).split(':')[1] || '?';
|
|
13
18
|
const defaultState = { lang: 'de', light: false, previous: null };
|
|
14
19
|
let state = { ...defaultState };
|
|
15
20
|
|
|
@@ -80,6 +85,49 @@ function statsLine() {
|
|
|
80
85
|
' | ' + t('statCodec') + ': ' + codecLabel(t, currentCodec());
|
|
81
86
|
}
|
|
82
87
|
|
|
88
|
+
const MINI_DEFAULTS = { s95bMiniPos: 'br', s95bMiniSize: 'medium', s95bMiniBrowse: 'play' };
|
|
89
|
+
const MINI_VALUES = { s95bMiniPos: MINI_POSITIONS, s95bMiniSize: MINI_SIZES, s95bMiniBrowse: MINI_BROWSE };
|
|
90
|
+
const MINI_LABELS = { br: 'posBR', bl: 'posBL', tr: 'posTR', tl: 'posTL', small: 'sizeSmall', medium: 'sizeMedium',
|
|
91
|
+
large: 'sizeLarge', play: 'browsePlay', pause: 'browsePause' };
|
|
92
|
+
|
|
93
|
+
function miniChoice(title, key, hint) {
|
|
94
|
+
return { name: t(title), value: null, menuId: 's95b-' + key,
|
|
95
|
+
menuHeader: { title: t(title), subtitle: hint ? t(hint) : t('miniHint') },
|
|
96
|
+
options: MINI_VALUES[key].map(value => ({ name: t(MINI_LABELS[value]), key, value })) };
|
|
97
|
+
}
|
|
98
|
+
function miniMenu() {
|
|
99
|
+
return { name: t('mini'), icon: 'CLEAR_COOKIES', value: null, menuId: 's95b-mini',
|
|
100
|
+
menuHeader: { title: t('mini'), subtitle: t('miniHint') },
|
|
101
|
+
options: [
|
|
102
|
+
// TizenTube's own switch; the fork extends it to the "MiniPlayer" row in the video menu.
|
|
103
|
+
{ name: t('miniOn'), subtitle: t('miniOnHint'), value: 'enableMPButton' },
|
|
104
|
+
miniChoice('miniPos', 's95bMiniPos'),
|
|
105
|
+
miniChoice('miniSize', 's95bMiniSize'),
|
|
106
|
+
miniChoice('miniBrowse', 's95bMiniBrowse', 'miniBrowseHint')
|
|
107
|
+
] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Exit: the same call TizenBrew's own Back key uses; YouTube's exit signal is the fallback.
|
|
111
|
+
function canExitViaTizen() {
|
|
112
|
+
try { return !!(window.tizen && window.tizen.application && typeof window.tizen.application.getCurrentApplication === 'function'); }
|
|
113
|
+
catch (_) { return false; }
|
|
114
|
+
}
|
|
115
|
+
function exitMenu() {
|
|
116
|
+
return { name: t('exit'), value: null, menuId: 's95b-exit',
|
|
117
|
+
menuHeader: { title: t('exit'), subtitle: canExitViaTizen() ? t('exitHintTizen') : t('exitHintSignal') },
|
|
118
|
+
options: [
|
|
119
|
+
{ name: t('exitCancel'), key: 's95bExit', value: 'no' },
|
|
120
|
+
{ name: t('exitNow'), key: 's95bExit', value: 'now' }
|
|
121
|
+
] };
|
|
122
|
+
}
|
|
123
|
+
function exitApp() {
|
|
124
|
+
if (canExitViaTizen()) {
|
|
125
|
+
try { window.tizen.application.getCurrentApplication().exit(); return; } catch (_) { /* try the signal */ }
|
|
126
|
+
}
|
|
127
|
+
try { resolveCommand({ signalAction: { signal: 'EXIT_APP' } }); }
|
|
128
|
+
catch (e) { showToast('TizenTube', t('exitFailed')); }
|
|
129
|
+
}
|
|
130
|
+
|
|
83
131
|
function buildOptions() {
|
|
84
132
|
return [
|
|
85
133
|
{ name: t('language'), value: null, menuId: 's95b-language',
|
|
@@ -90,7 +138,9 @@ function buildOptions() {
|
|
|
90
138
|
] },
|
|
91
139
|
codecMenu(t),
|
|
92
140
|
qualityMenu(t),
|
|
93
|
-
|
|
141
|
+
miniMenu(),
|
|
142
|
+
{ name: t('light'), subtitle: t('lightHint'), value: 's95bLowOverhead' },
|
|
143
|
+
exitMenu()
|
|
94
144
|
];
|
|
95
145
|
}
|
|
96
146
|
|
|
@@ -98,7 +148,7 @@ function openMenu() {
|
|
|
98
148
|
try {
|
|
99
149
|
optionShow({
|
|
100
150
|
menuId: 's95b-menu',
|
|
101
|
-
menuHeader: { title: t('title'), subtitle: statsLine() },
|
|
151
|
+
menuHeader: { title: t('title') + ' v' + VERSION, subtitle: statsLine() },
|
|
102
152
|
selectedIndex: 0,
|
|
103
153
|
options: buildOptions()
|
|
104
154
|
}, false);
|
|
@@ -119,13 +169,21 @@ function onConfigChange(event) {
|
|
|
119
169
|
const detail = event && event.detail;
|
|
120
170
|
if (!detail) return;
|
|
121
171
|
if (detail.key === 's95bLowOverhead') turnLight(detail.value === true);
|
|
122
|
-
else if (detail.key === '
|
|
172
|
+
else if (detail.key === 's95bExit') {
|
|
173
|
+
if (detail.value !== 'now') return;
|
|
174
|
+
configWrite('s95bExit', 'no'); // never leave "now" stored
|
|
175
|
+
exitApp();
|
|
176
|
+
} else if (detail.key === 's95bLanguage') { state.lang = detail.value === 'en' ? 'en' : 'de'; save(); }
|
|
123
177
|
}
|
|
124
178
|
|
|
125
179
|
function init() {
|
|
126
180
|
// Menu entries read their current value from the TizenTube config, so mirror our stored state into it first.
|
|
127
181
|
if (configRead('s95bLowOverhead') !== state.light) configWrite('s95bLowOverhead', state.light);
|
|
128
182
|
if (configRead('s95bLanguage') !== state.lang) configWrite('s95bLanguage', state.lang);
|
|
183
|
+
if (configRead('s95bExit') !== 'no') configWrite('s95bExit', 'no');
|
|
184
|
+
Object.keys(MINI_DEFAULTS).forEach(key => {
|
|
185
|
+
if (MINI_VALUES[key].indexOf(configRead(key)) < 0) configWrite(key, MINI_DEFAULTS[key]);
|
|
186
|
+
});
|
|
129
187
|
configChangeEmitter.addEventListener('configChange', onConfigChange);
|
|
130
188
|
// Capture phase on window: only the yellow key is consumed.
|
|
131
189
|
window.addEventListener('keydown', onKeys, true);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* S95B Performance Edition – GPL-3.0-only
|
|
2
|
+
* Settings for TizenTube's own mini player (features/pictureInPicture.js): position, size and
|
|
3
|
+
* whether the video keeps playing while browsing. Called from the two patched lines in pictureInPicture.js.
|
|
4
|
+
* Defaults reproduce upstream exactly: 1/3.5 of the screen at 68vh / 68vw (bottom right), keeps playing.
|
|
5
|
+
*/
|
|
6
|
+
import { configRead } from '../config.js';
|
|
7
|
+
|
|
8
|
+
export const MINI_POSITIONS = ['br', 'bl', 'tr', 'tl'];
|
|
9
|
+
export const MINI_SIZES = ['small', 'medium', 'large'];
|
|
10
|
+
export const MINI_BROWSE = ['play', 'pause'];
|
|
11
|
+
const DIVISOR = { small: 4.5, medium: 3.5, large: 2.5 };
|
|
12
|
+
// Upstream's gap between the mini player and the right/bottom screen edge, in vw/vh (100 - 68 - 100/3.5).
|
|
13
|
+
const GAP = 100 - 68 - 100 / 3.5;
|
|
14
|
+
const round = value => Math.round(value * 100) / 100;
|
|
15
|
+
|
|
16
|
+
export function s95bMiniLayout(videoElement) {
|
|
17
|
+
const position = MINI_POSITIONS.indexOf(configRead('s95bMiniPos')) >= 0 ? configRead('s95bMiniPos') : 'br';
|
|
18
|
+
const divisor = DIVISOR[configRead('s95bMiniSize')] || DIVISOR.medium;
|
|
19
|
+
const far = round(100 - 100 / divisor - GAP);
|
|
20
|
+
const near = round(GAP);
|
|
21
|
+
videoElement.style.width = `${window.innerWidth / divisor}px`;
|
|
22
|
+
videoElement.style.height = `${window.innerHeight / divisor}px`;
|
|
23
|
+
videoElement.style.top = (position[0] === 'b' ? far : near) + 'vh';
|
|
24
|
+
videoElement.style.left = (position[1] === 'r' ? far : near) + 'vw';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// "Pause while browsing": pause once the reloaded video has really started, so YouTube's own play() call
|
|
28
|
+
// is not interrupted. Going back to full screen starts the video again from this position (upstream behavior).
|
|
29
|
+
export function s95bMiniEntered(videoElement) {
|
|
30
|
+
if (configRead('s95bMiniBrowse') !== 'pause') return;
|
|
31
|
+
const pauseOnce = () => {
|
|
32
|
+
videoElement.removeEventListener('playing', pauseOnce);
|
|
33
|
+
if (window.isPipPlaying) videoElement.pause();
|
|
34
|
+
};
|
|
35
|
+
videoElement.addEventListener('playing', pauseOnce);
|
|
36
|
+
}
|
package/mods/resolveCommand.js
CHANGED
|
@@ -97,7 +97,7 @@ export function patchResolveCommand() {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
cmd.openPopupAction.popup.overlaySectionRenderer.overlay.overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer.content.overlayPanelItemListRenderer.items.splice(2, 0,
|
|
100
|
+
if (configRead('enableMPButton')) cmd.openPopupAction.popup.overlaySectionRenderer.overlay.overlayTwoPanelRenderer.actionPanel.overlayPanelRenderer.content.overlayPanelItemListRenderer.items.splice(2, 0,
|
|
101
101
|
buttonItem(
|
|
102
102
|
{ title: t('player.miniPlayer') },
|
|
103
103
|
{ icon: 'CLEAR_COOKIES' }, [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxymoron.13/tizentube-s95b",
|
|
3
|
-
"appName": "TizenTube S95B
|
|
4
|
-
"version": "0.3.
|
|
3
|
+
"appName": "TizenTube S95B 0.3.2 (Fork)",
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"description": "Unofficial fork of TizenTube (@foxreis/tizentube) for TizenBrew: reversible performance profile, German/English settings, smaller bundle.",
|
|
6
6
|
"packageType": "mods",
|
|
7
7
|
"websiteURL": "https://youtube.com/tv?additionalDataUrl=http%3A%2F%2Flocalhost%3A8085%2Fdial%2Fapps%2FYouTube",
|