diff --git a/__pycache__/player.cpython-313.pyc b/__pycache__/player.cpython-313.pyc index c9c4749..26609ce 100644 Binary files a/__pycache__/player.cpython-313.pyc and b/__pycache__/player.cpython-313.pyc differ diff --git a/__pycache__/server.cpython-313.pyc b/__pycache__/server.cpython-313.pyc index 269f5ee..c245f3e 100644 Binary files a/__pycache__/server.cpython-313.pyc and b/__pycache__/server.cpython-313.pyc differ diff --git a/player.py b/player.py index 791c85a..06278ee 100755 --- a/player.py +++ b/player.py @@ -144,7 +144,7 @@ class Player: self.player['video-latency-hacks'] = 'yes' if self.low_latency else 'no' self.player['stream-buffer-size'] = '4k' if self.low_latency else '128k' return self.low_latency - + def stop(self): """Stop the player.""" self.player.stop() diff --git a/server.py b/server.py index a2cb739..74f45fa 100755 --- a/server.py +++ b/server.py @@ -87,7 +87,35 @@ class IPMPVServer: @self.app.route("/toggle_mute") def 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): """Handle the index route.""" from channels import group_channels @@ -178,6 +206,28 @@ class IPMPVServer: thread.start() 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): """Handle the toggle_deinterlace route.""" state = self.player.toggle_deinterlace() @@ -219,26 +269,6 @@ class IPMPVServer: self.resolution = change_resolution(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): """Handle the volume_up route.""" if self.volume_control: diff --git a/templates/index.html b/templates/index.html index 4596df9..5b406c1 100644 --- a/templates/index.html +++ b/templates/index.html @@ -37,6 +37,74 @@ 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 { max-width: 1200px; margin: 0 auto; @@ -229,6 +297,9 @@ .section { margin: 20px 0; padding: 10px; + display: flex; + flex-direction: column; + align-items: center; border-radius: var(--border-radius); } @@ -276,7 +347,7 @@ /* Mobile touch improvements */ @media (max-width: 767px) { - button, + button:not(.dpad-button,.leftbtn,.rightbtn,.midbtn), input { padding: 14px; /* Larger touch targets */ @@ -346,18 +417,9 @@ margin-top: 8px; } - #osd-on-btn { - margin-top: 8px; - margin-bottom: 8px; - margin-left: 4px; - min-width: 100px; - } - - #osd-off-btn { - margin-top: 8px; - margin-bottom: 8px; - margin-right: 4px; - min-width: 100px; + .leftbtn, .rightbtn, .midbtn { + width: 90%; + padding: 14px; } } @@ -368,6 +430,15 @@

Welcome to IPMPV

Current Channel: %CURRENT_CHANNEL%

+
+
+ + + + +
+
+
-
+

Volume

- + - +
@@ -532,30 +603,42 @@ fetch(`/hide_osd`).then(response => response.json()); } - function volumeUp() { - fetch(`/volume_up`) - .then(response => response.json()) - .then(data => { - showToast("Volume: "+data.volume+"%"); - }); - } + function volumeUp() { + fetch(`/volume_up`) + .then(response => response.json()) + .then(data => { + showToast("Volume: " + data.volume + "%"); + }); + } - function volumeDown() { - fetch(`/volume_down`) - .then(response => response.json()) - .then(data => { - showToast("Volume: "+data.volume+"%"); - }); - } + function volumeDown() { + fetch(`/volume_down`) + .then(response => response.json()) + .then(data => { + showToast("Volume: " + data.volume + "%"); + }); + } - function toggleMute() { - fetch(`/toggle_mute`) - .then(response => response.json()) - .then(data => { - muted = data.muted ? "yes" : "no"; - showToast("Muted: " + muted); - }); - } + function toggleMute() { + fetch(`/toggle_mute`) + .then(response => response.json()) + .then(data => { + muted = data.muted ? "yes" : "no"; + 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 function showToast(message) { @@ -586,4 +669,4 @@ - + \ No newline at end of file