Added a D-Pad
This commit is contained in:
parent
20597acd64
commit
d7f2f808df
Binary file not shown.
Binary file not shown.
@ -144,7 +144,7 @@ class Player:
|
|||||||
self.player['video-latency-hacks'] = 'yes' if self.low_latency else 'no'
|
self.player['video-latency-hacks'] = 'yes' if self.low_latency else 'no'
|
||||||
self.player['stream-buffer-size'] = '4k' if self.low_latency else '128k'
|
self.player['stream-buffer-size'] = '4k' if self.low_latency else '128k'
|
||||||
return self.low_latency
|
return self.low_latency
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Stop the player."""
|
"""Stop the player."""
|
||||||
self.player.stop()
|
self.player.stop()
|
||||||
|
|||||||
72
server.py
72
server.py
@ -87,7 +87,35 @@ class IPMPVServer:
|
|||||||
@self.app.route("/toggle_mute")
|
@self.app.route("/toggle_mute")
|
||||||
def toggle_mute():
|
def toggle_mute():
|
||||||
return self._handle_toggle_mute()
|
return self._handle_toggle_mute()
|
||||||
|
|
||||||
|
@self.app.route("/channel_up")
|
||||||
|
def channel_up():
|
||||||
|
return self._handle_channel_up()
|
||||||
|
|
||||||
|
@self.app.route("/channel_down")
|
||||||
|
def channel_down():
|
||||||
|
return self._handle_channel_down()
|
||||||
|
|
||||||
|
@self.app.route('/manifest.json')
|
||||||
|
def serve_manifest():
|
||||||
|
return send_from_directory("static", 'manifest.json',
|
||||||
|
mimetype='application/manifest+json')
|
||||||
|
|
||||||
|
@self.app.route('/icon512_rounded.png')
|
||||||
|
def serve_rounded_icon():
|
||||||
|
return send_from_directory("static", 'icon512_rounded.png',
|
||||||
|
mimetype='image/png')
|
||||||
|
|
||||||
|
@self.app.route('/icon512_maskable.png')
|
||||||
|
def serve_maskable_icon():
|
||||||
|
return send_from_directory("static", 'icon512_maskable.png',
|
||||||
|
mimetype='image/png')
|
||||||
|
|
||||||
|
@self.app.route('/screenshot1.png')
|
||||||
|
def serve_screenshot_1():
|
||||||
|
return send_from_directory("static", 'screenshot1.png',
|
||||||
|
mimetype='image/png')
|
||||||
|
|
||||||
def _handle_index(self):
|
def _handle_index(self):
|
||||||
"""Handle the index route."""
|
"""Handle the index route."""
|
||||||
from channels import group_channels
|
from channels import group_channels
|
||||||
@ -178,6 +206,28 @@ class IPMPVServer:
|
|||||||
thread.start()
|
thread.start()
|
||||||
return "", 204
|
return "", 204
|
||||||
|
|
||||||
|
def _handle_channel_up(self):
|
||||||
|
"""Handle the channel_up route."""
|
||||||
|
index = self.player.current_index + 1 if self.player.current_index is not None else 0;
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self.player.play_channel,
|
||||||
|
args=(index,self.channels),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
return "", 204
|
||||||
|
|
||||||
|
def _handle_channel_down(self):
|
||||||
|
"""Handle the channel_down route."""
|
||||||
|
index = self.player.current_index - 1 if self.player.current_index is not None else -1;
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self.player.play_channel,
|
||||||
|
args=(index,self.channels),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
return "", 204
|
||||||
|
|
||||||
def _handle_toggle_deinterlace(self):
|
def _handle_toggle_deinterlace(self):
|
||||||
"""Handle the toggle_deinterlace route."""
|
"""Handle the toggle_deinterlace route."""
|
||||||
state = self.player.toggle_deinterlace()
|
state = self.player.toggle_deinterlace()
|
||||||
@ -219,26 +269,6 @@ class IPMPVServer:
|
|||||||
self.resolution = change_resolution(self.resolution)
|
self.resolution = change_resolution(self.resolution)
|
||||||
return jsonify(res=self.resolution)
|
return jsonify(res=self.resolution)
|
||||||
|
|
||||||
@self.app.route('/manifest.json')
|
|
||||||
def serve_manifest():
|
|
||||||
return send_from_directory("static", 'manifest.json',
|
|
||||||
mimetype='application/manifest+json')
|
|
||||||
|
|
||||||
@self.app.route('/icon512_rounded.png')
|
|
||||||
def serve_rounded_icon():
|
|
||||||
return send_from_directory("static", 'icon512_rounded.png',
|
|
||||||
mimetype='image/png')
|
|
||||||
|
|
||||||
@self.app.route('/icon512_maskable.png')
|
|
||||||
def serve_maskable_icon():
|
|
||||||
return send_from_directory("static", 'icon512_maskable.png',
|
|
||||||
mimetype='image/png')
|
|
||||||
|
|
||||||
@self.app.route('/screenshot1.png')
|
|
||||||
def serve_screenshot_1():
|
|
||||||
return send_from_directory("static", 'screenshot1.png',
|
|
||||||
mimetype='image/png')
|
|
||||||
|
|
||||||
def _handle_volume_up(self):
|
def _handle_volume_up(self):
|
||||||
"""Handle the volume_up route."""
|
"""Handle the volume_up route."""
|
||||||
if self.volume_control:
|
if self.volume_control:
|
||||||
|
|||||||
@ -37,6 +37,74 @@
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dpad-container {
|
||||||
|
position: relative;
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dpad-container::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 80px;
|
||||||
|
left: 80px;
|
||||||
|
content: "";
|
||||||
|
background-color: var(--secondary-bg);
|
||||||
|
color: white;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dpad-button {
|
||||||
|
position: absolute;
|
||||||
|
min-width: 80px;
|
||||||
|
min-height: 80px;
|
||||||
|
margin: 0;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background-color 0.2s, transform 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dpad-button:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dpad-button:active {
|
||||||
|
background-color: #777;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dpad-up {
|
||||||
|
top: 0;
|
||||||
|
left: 80px;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dpad-right {
|
||||||
|
top: 80px;
|
||||||
|
right: 0;
|
||||||
|
border-radius: 0 10px 10px 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dpad-down {
|
||||||
|
bottom: 0;
|
||||||
|
left: 80px;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dpad-left {
|
||||||
|
top: 80px;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 10px 0 0 10px;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -229,6 +297,9 @@
|
|||||||
.section {
|
.section {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,7 +347,7 @@
|
|||||||
/* Mobile touch improvements */
|
/* Mobile touch improvements */
|
||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
|
|
||||||
button,
|
button:not(.dpad-button,.leftbtn,.rightbtn,.midbtn),
|
||||||
input {
|
input {
|
||||||
padding: 14px;
|
padding: 14px;
|
||||||
/* Larger touch targets */
|
/* Larger touch targets */
|
||||||
@ -346,18 +417,9 @@
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#osd-on-btn {
|
.leftbtn, .rightbtn, .midbtn {
|
||||||
margin-top: 8px;
|
width: 90%;
|
||||||
margin-bottom: 8px;
|
padding: 14px;
|
||||||
margin-left: 4px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#osd-off-btn {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
margin-right: 4px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -368,6 +430,15 @@
|
|||||||
<h1>Welcome to IPMPV</h1>
|
<h1>Welcome to IPMPV</h1>
|
||||||
<p>Current Channel: <span id="current-channel">%CURRENT_CHANNEL%</span></p>
|
<p>Current Channel: <span id="current-channel">%CURRENT_CHANNEL%</span></p>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<div class="dpad-container">
|
||||||
|
<button id="dpad-up" class="dpad-button" onclick="channelUp()">↑</button>
|
||||||
|
<button id="dpad-right" class="dpad-button" onclick="volumeUp()">→</button>
|
||||||
|
<button id="dpad-down" class="dpad-button" onclick="channelDown()">↓</button>
|
||||||
|
<button id="dpad-left" class="dpad-button" onclick="volumeDown()">←</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<button class="control-button" onclick="stopPlayer()">Stop</button>
|
<button class="control-button" onclick="stopPlayer()">Stop</button>
|
||||||
<button id="retroarch-btn" class="%RETROARCH_STATE%" onclick="toggleRetroArch()">
|
<button id="retroarch-btn" class="%RETROARCH_STATE%" onclick="toggleRetroArch()">
|
||||||
@ -381,12 +452,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Volume</h2>
|
<h2>Volume</h2>
|
||||||
<div class="osd-toggle">
|
<div class="osd-toggle">
|
||||||
<button class="leftbtn" id="vol-up-btn" onclick="volumeUp()">+</button>
|
<button class="leftbtn" id="vol-up-btn" onclick="volumeDown()">-</button>
|
||||||
<button class="midbtn %MUTE_STATE%" id="vol-mute-btn" onclick="toggleMute()">mute</button>
|
<button class="midbtn %MUTE_STATE%" id="vol-mute-btn" onclick="toggleMute()">mute</button>
|
||||||
<button class="rightbtn" id="vol-dn-btn" onclick="volumeDown()">-</button>
|
<button class="rightbtn" id="vol-dn-btn" onclick="volumeUp()">+</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -532,30 +603,42 @@
|
|||||||
fetch(`/hide_osd`).then(response => response.json());
|
fetch(`/hide_osd`).then(response => response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
function volumeUp() {
|
function volumeUp() {
|
||||||
fetch(`/volume_up`)
|
fetch(`/volume_up`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
showToast("Volume: "+data.volume+"%");
|
showToast("Volume: " + data.volume + "%");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function volumeDown() {
|
function volumeDown() {
|
||||||
fetch(`/volume_down`)
|
fetch(`/volume_down`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
showToast("Volume: "+data.volume+"%");
|
showToast("Volume: " + data.volume + "%");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMute() {
|
function toggleMute() {
|
||||||
fetch(`/toggle_mute`)
|
fetch(`/toggle_mute`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
muted = data.muted ? "yes" : "no";
|
muted = data.muted ? "yes" : "no";
|
||||||
showToast("Muted: " + muted);
|
showToast("Muted: " + muted);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function channelUp() {
|
||||||
|
showToast("Loading channel...");
|
||||||
|
fetch(`/channel_up`)
|
||||||
|
.then(() => window.location.reload())
|
||||||
|
}
|
||||||
|
|
||||||
|
function channelDown() {
|
||||||
|
showToast("Loading channel...");
|
||||||
|
fetch(`/channel_down`)
|
||||||
|
.then(() => window.location.reload())
|
||||||
|
}
|
||||||
|
|
||||||
// Mobile-friendly toast notification
|
// Mobile-friendly toast notification
|
||||||
function showToast(message) {
|
function showToast(message) {
|
||||||
@ -586,4 +669,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user