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 @@
Current Channel: %CURRENT_CHANNEL%
+